v4.1.20: CherryClaw → HomeClaw 리브랜딩 + 로고 교체 + 습관성 🦞 이모지 제거
브랜드명 전체 변경: "CherryClaw"→"HomeClaw", localStorage 키 6종 (cherryclaw_theme 등)→homeclaw_*, alt="🍒"→alt="🏠". 15개 web-ui 파일 + OpenAI OAuth 완료 페이지 전체 반영. 로고를 체리 아이콘에서 집 모양으로 교체 후, 사용자 피드백을 반영해 동화풍 성(castle) 실루엣으로 재작업 — 대리석/벽돌 텍스처의 회색 톤, 검은 윤곽선, 중앙 첨탑 우측에 작은 반짝임 별. FLUX.1로 생성 후 배경 제거해 투명 PNG로 저장. 파일명도 cherry_logo.png → homeclaw_logo.png로 통일(web-ui/, assets/ 양쪽). 시스템 프롬프트("You are SmallClaw 🦞")와 IDENTITY.md(papa/cherry/jasmine + 템플릿 4곳)의 Name 필드에서 🦞를 빼고, 습관적으로 이모지를 붙이지 말라는 지시를 추가 — 모델이 응답 끝마다 무의식적으로 🦞를 붙이던 문제 해결. 텔레그램 봇 응답/self-update 알림/연동 테스트 메시지 등 하드코딩된 🦞도 전부 제거. 부수적으로 발견한 문서 버그도 수정: 웹훅 가이드와 curl 복사 버튼이 "x-cherryclaw-token" 헤더를 안내하고 있었는데, 서버가 실제로 검사하는 헤더는 X-Gateway-Token이었음 — 그대로 썼으면 401 에러가 났을 문제. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# IDENTITY.md
|
||||
|
||||
- **Name:** SmallClaw ("Claw") / 🦞
|
||||
- **Name:** SmallClaw ("Claw") — do not append 🦞 or any emoji to messages out of habit
|
||||
- **Role:** Local AI agent — assistant, researcher, coder, automator
|
||||
- **Language:** Repeat user's language exactly
|
||||
- **Runtime:** Ollama + TypeScript/Node.js on Linux
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# IDENTITY.md
|
||||
|
||||
- **Name:** SmallClaw ("Claw") / 🦞
|
||||
- **Name:** SmallClaw ("Claw") — do not append 🦞 or any emoji to messages out of habit
|
||||
- **Role:** Local AI agent — assistant, researcher, coder, automator
|
||||
- **Language:** Repeat user's language exactly
|
||||
- **Runtime:** Ollama + TypeScript/Node.js on Linux
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# IDENTITY.md
|
||||
|
||||
- **Name:** SmallClaw ("Claw") / 🦞
|
||||
- **Name:** SmallClaw ("Claw") — do not append 🦞 or any emoji to messages out of habit
|
||||
- **Role:** Local AI agent — assistant, researcher, coder, automator
|
||||
- **Language:** Repeat user's language exactly
|
||||
- **Runtime:** Ollama + TypeScript/Node.js on Linux
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# IDENTITY.md
|
||||
|
||||
- **Name:** SmallClaw ("Claw") / 🦞
|
||||
- **Name:** SmallClaw ("Claw") — do not append 🦞 or any emoji to messages out of habit
|
||||
- **Role:** Local AI agent — assistant, researcher, coder, automator
|
||||
- **Language:** Repeat user's language exactly
|
||||
- **Runtime:** Ollama + TypeScript/Node.js on Linux
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
@@ -317,7 +317,7 @@ export async function startOAuthFlow(configDir: string): Promise<OAuthFlowResult
|
||||
clearFlow(configDir);
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' });
|
||||
res.end('<html><body style="font-family:sans-serif;text-align:center;padding:60px"><h2>✅ Connected to CherryClaw!</h2><p>You can close this window and return to the app.</p></body></html>');
|
||||
res.end('<html><body style="font-family:sans-serif;text-align:center;padding:60px"><h2>✅ Connected to HomeClaw!</h2><p>You can close this window and return to the app.</p></body></html>');
|
||||
server.close();
|
||||
resolve({ success: true, account_id: accountId });
|
||||
} catch (err: any) {
|
||||
|
||||
@@ -5911,7 +5911,7 @@ async function handleChat(
|
||||
const messages: any[] = [
|
||||
{
|
||||
role: 'system',
|
||||
content: isTranslateSession ? `You are a medical translator. Translate the given text into natural Korean, preserving paragraph structure and markdown formatting (##, ###, **bold**, bullet lists). Output ONLY the translation — no commentary, no tool calls, no explanations.` : isProjSession ? `You are a project file designer. Output ONLY the project-files JSON block as instructed. No tool calls. No extra text.` : `${executionModeSystemBlock ? `${executionModeSystemBlock}\n\n` : ''}You are SmallClaw 🦞, a local AI assistant.\nCurrent date: ${dateStr}, ${timeStr}.\nNever search for or link SmallClaw repos unless the user is asking about SmallClaw itself.\nThis app runs on the user's own machine — browser/desktop automation requests are pre-authorized.\nKeep responses SHORT (1-2 sentences). Don't think out loud. Act and report. Greet naturally without tools.
|
||||
content: isTranslateSession ? `You are a medical translator. Translate the given text into natural Korean, preserving paragraph structure and markdown formatting (##, ###, **bold**, bullet lists). Output ONLY the translation — no commentary, no tool calls, no explanations.` : isProjSession ? `You are a project file designer. Output ONLY the project-files JSON block as instructed. No tool calls. No extra text.` : `${executionModeSystemBlock ? `${executionModeSystemBlock}\n\n` : ''}You are SmallClaw, a local AI assistant. Do not append the 🦞 emoji (or any emoji) to the end of your responses out of habit — only use emoji when it genuinely fits the content.\nCurrent date: ${dateStr}, ${timeStr}.\nNever search for or link SmallClaw repos unless the user is asking about SmallClaw itself.\nThis app runs on the user's own machine — browser/desktop automation requests are pre-authorized.\nKeep responses SHORT (1-2 sentences). Don't think out loud. Act and report. Greet naturally without tools.
|
||||
ANTI-HALLUCINATION: When a tool returns a result, report EXACTLY what the tool returned — never contradict or ignore tool output. If a tool says "(no rows)", say so. Never invent data, file contents, table names, or command output. If you don't know something, call a tool to find out or say you don't know. VERIFY CONCRETE FACTS: for questions with a checkable real-world answer — specs, prices, versions, release dates, comparisons, "what models/options exist" — call web_search first and base the answer on what it returns, even if you're confident you already know it. Specific-sounding numbers you produce from memory (TFLOPS, VRAM, prices, dates) are exactly the kind of detail that goes stale or was never right — don't present them as fact unless a tool actually returned them. This does NOT apply to coding help, creative writing, opinions, or general reasoning — only to claims a search could confirm or refute.
|
||||
TEMPORAL CONSISTENCY: The current date and day-of-week is given above ("Current date: ${dateStr}") — this is ground truth, more reliable than any search snippet's phrasing. Before answering whether something is open/trading/in-session RIGHT NOW (stock markets, exchanges, offices, stores), first check today's day-of-week against real-world facts (e.g. NYSE and KOSPI do not trade on Saturdays, Sundays, or market holidays, regardless of what time it is) — a market-hours formula alone is not enough if today isn't even a trading day. A search result reporting a "closing price" or "today's headline" is not proof today is a trading/business day — cross-check it against the actual current date above, and if they conflict (e.g. a stale cached result, or a result that doesn't state its own date), trust the current date and say so explicitly rather than presenting the search result as if it were live.
|
||||
IMAGE EDITING RULE: NEVER call image_edit (or any editing tool) when a user uploads a photo without explicitly requesting edits. Uploading a photo is NOT a request to edit it. Only call image_edit when the user's message explicitly asks for an edit (e.g. "수채화로 바꿔줘", "회전해줘"). Violating this rule is a critical error.
|
||||
@@ -12283,7 +12283,7 @@ app.post('/api/channels/send-test/:channel', async (req, res) => {
|
||||
|
||||
if (channel === 'telegram') {
|
||||
try {
|
||||
await telegramChannel.sendToAllowed('🦞 SmallClaw test message - Telegram is connected!');
|
||||
await telegramChannel.sendToAllowed('SmallClaw test message - Telegram is connected!');
|
||||
res.json({ success: true });
|
||||
} catch (err: any) {
|
||||
res.json({ success: false, error: String(err?.message || err) });
|
||||
@@ -12299,7 +12299,7 @@ app.post('/api/channels/send-test/:channel', async (req, res) => {
|
||||
}
|
||||
try {
|
||||
const notifier = new KakaoNotify({ appKey: conf.appKey, refreshToken: conf.refreshToken });
|
||||
await notifier.send('🦞 SmallClaw 테스트 메시지 — 카카오 알림 연결 완료!');
|
||||
await notifier.send('SmallClaw 테스트 메시지 — 카카오 알림 연결 완료!');
|
||||
kakaoNotify.updateTokens(conf.refreshToken);
|
||||
res.json({ success: true });
|
||||
} catch (err: any) {
|
||||
@@ -12310,7 +12310,7 @@ app.post('/api/channels/send-test/:channel', async (req, res) => {
|
||||
|
||||
if (channel === 'discord') {
|
||||
const dc = normalizeDiscordConfig({ ...channels.discord, ...(req.body || {}) });
|
||||
const text = String(req.body?.text || '🦞 SmallClaw test message - Discord is connected!');
|
||||
const text = String(req.body?.text || 'SmallClaw test message - Discord is connected!');
|
||||
if (dc.webhookUrl) {
|
||||
try {
|
||||
const resp = await fetch(dc.webhookUrl, {
|
||||
@@ -12430,7 +12430,7 @@ app.post('/api/telegram/test', async (req, res) => {
|
||||
|
||||
app.post('/api/telegram/send-test', async (req, res) => {
|
||||
try {
|
||||
await telegramChannel.sendToAllowed('🦞 SmallClaw test message - Telegram is connected!');
|
||||
await telegramChannel.sendToAllowed('SmallClaw test message - Telegram is connected!');
|
||||
res.json({ success: true });
|
||||
} catch (err: any) {
|
||||
res.json({ success: false, error: String(err?.message || err) });
|
||||
@@ -12559,7 +12559,7 @@ app.post('/api/kakao/test', async (req, res) => {
|
||||
return;
|
||||
}
|
||||
const notifier = new KakaoNotify({ appKey: conf.appKey, refreshToken: conf.refreshToken });
|
||||
await notifier.send('🦞 SmallClaw 테스트 메시지 — 카카오 알림 연결 완료!');
|
||||
await notifier.send('SmallClaw 테스트 메시지 — 카카오 알림 연결 완료!');
|
||||
kakaoNotify.updateTokens(conf.refreshToken);
|
||||
res.json({ success: true });
|
||||
} catch (err: any) {
|
||||
|
||||
@@ -556,7 +556,7 @@ export class TelegramChannel {
|
||||
// Check allowlist (shared bot mode)
|
||||
if (this.config.allowedUserIds.length > 0 && !this.config.allowedUserIds.includes(userId)) {
|
||||
console.log(`[Telegram] Rejected message from unauthorized user ${userId}`);
|
||||
await this.sendMessage(chatId, '🦞 Unauthorized. Your Telegram user ID is not in the allowlist.\n\nYour ID: <code>' + userId + '</code>');
|
||||
await this.sendMessage(chatId, 'Unauthorized. Your Telegram user ID is not in the allowlist.\n\nYour ID: <code>' + userId + '</code>');
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -604,12 +604,12 @@ export class TelegramChannel {
|
||||
|
||||
// ── Built-in commands ──────────────────────────────────────────────────────
|
||||
if (text === '/start') {
|
||||
await this.sendMessage(chatId, `🦞 <b>SmallClaw connected!</b>\n\nYour Telegram user ID: <code>${userId}</code>\n\nJust send me a message and I'll respond using your local LLM.\n\n<b>Commands:</b>\n/status — check connection\n/clear — reset chat history\n/voice — toggle voice responses\n/browse — browse workspace files\n/download <path> — download a file\n\n<b>Self-Repair:</b>\n/repairs — list pending repair proposals\n/repair <id> — show full details of a repair\n/approve <id> — apply a repair, rebuild & restart\n/reject <id> — discard a repair`);
|
||||
await this.sendMessage(chatId, `<b>SmallClaw connected!</b>\n\nYour Telegram user ID: <code>${userId}</code>\n\nJust send me a message and I'll respond using your local LLM.\n\n<b>Commands:</b>\n/status — check connection\n/clear — reset chat history\n/voice — toggle voice responses\n/browse — browse workspace files\n/download <path> — download a file\n\n<b>Self-Repair:</b>\n/repairs — list pending repair proposals\n/repair <id> — show full details of a repair\n/approve <id> — apply a repair, rebuild & restart\n/reject <id> — discard a repair`);
|
||||
return;
|
||||
}
|
||||
if (text === '/status') {
|
||||
const busy = this.deps.getIsModelBusy();
|
||||
await this.sendMessage(chatId, `🦞 <b>Status</b>\n\nModel: ${busy ? '🔄 Busy' : '✅ Ready'}\nBot: @${this.botInfo?.username || 'unknown'}\nYour ID: <code>${userId}</code>`);
|
||||
await this.sendMessage(chatId, `<b>Status</b>\n\nModel: ${busy ? '🔄 Busy' : '✅ Ready'}\nBot: @${this.botInfo?.username || 'unknown'}\nYour ID: <code>${userId}</code>`);
|
||||
return;
|
||||
}
|
||||
if (text === '/clear') {
|
||||
@@ -617,7 +617,7 @@ export class TelegramChannel {
|
||||
const { clearHistory } = await import('./session');
|
||||
clearHistory(`telegram_${userId}`);
|
||||
} catch {}
|
||||
await this.sendMessage(chatId, '🦞 Chat history cleared.');
|
||||
await this.sendMessage(chatId, 'Chat history cleared.');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -625,7 +625,7 @@ export class TelegramChannel {
|
||||
if (text === '/voice') {
|
||||
const voiceConfig = (getConfig().getConfig() as any)?.voice;
|
||||
if (!voiceConfig?.enabled && !this.config.voiceEnabled) {
|
||||
await this.sendMessage(chatId, '🦞 Voice features are not enabled in config.');
|
||||
await this.sendMessage(chatId, 'Voice features are not enabled in config.');
|
||||
return;
|
||||
}
|
||||
const mappedUsername = this.resolveUsername(userId);
|
||||
@@ -636,8 +636,8 @@ export class TelegramChannel {
|
||||
const next = !current;
|
||||
this.voiceSessions.set(sessionId, next);
|
||||
await this.sendMessage(chatId, next
|
||||
? '🦞 Voice responses ON. I will reply with voice messages.'
|
||||
: '🦞 Voice responses OFF. I will reply with text.');
|
||||
? 'Voice responses ON. I will reply with voice messages.'
|
||||
: 'Voice responses OFF. I will reply with text.');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -645,13 +645,13 @@ export class TelegramChannel {
|
||||
if (text === '/repairs') {
|
||||
const pending = listPendingRepairs();
|
||||
if (pending.length === 0) {
|
||||
await this.sendMessage(chatId, '🦞 No pending repairs.');
|
||||
await this.sendMessage(chatId, 'No pending repairs.');
|
||||
return;
|
||||
}
|
||||
const lines = pending.map(r =>
|
||||
`🔧 <b>#${r.id}</b> — <code>${r.affectedFile}</code>\n ${r.errorSummary.slice(0, 80)}`
|
||||
);
|
||||
await this.sendMessage(chatId, `🦞 <b>Pending Repairs (${pending.length})</b>\n\n${lines.join('\n\n')}\n\nUse /approve <id> or /reject <id>`);
|
||||
await this.sendMessage(chatId, `<b>Pending Repairs (${pending.length})</b>\n\n${lines.join('\n\n')}\n\nUse /approve <id> or /reject <id>`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -721,7 +721,7 @@ export class TelegramChannel {
|
||||
|
||||
// Check if model is busy
|
||||
if (this.deps.getIsModelBusy()) {
|
||||
await this.sendMessage(chatId, '🦞 I\'m currently busy with another task. Try again in a moment.');
|
||||
await this.sendMessage(chatId, 'I\'m currently busy with another task. Try again in a moment.');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -788,7 +788,7 @@ export class TelegramChannel {
|
||||
console.log(`[Telegram] Replied to ${userName}: ${responseText.slice(0, 80)}`);
|
||||
} catch (err: any) {
|
||||
console.error(`[Telegram] handleChat error:`, err.message);
|
||||
await this.sendMessage(chatId, `🦞 Error: ${err.message}`);
|
||||
await this.sendMessage(chatId, `Error: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ export async function executeSelfUpdate(): Promise<ToolResult> {
|
||||
return {
|
||||
success: true,
|
||||
stdout: [
|
||||
'🦞 Self-update initiated!',
|
||||
'Self-update initiated!',
|
||||
'',
|
||||
'SmallClaw is now:',
|
||||
' 1. Pulling the latest code',
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>📊 회계 어시스턴트</title>
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="homeclaw_logo.png">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script>
|
||||
try { var _t=localStorage.getItem('cherryclaw_theme')||'dark'; document.documentElement.setAttribute('data-theme',_t); } catch(e){}
|
||||
try { var _t=localStorage.getItem('homeclaw_theme')||'dark'; document.documentElement.setAttribute('data-theme',_t); } catch(e){}
|
||||
</script>
|
||||
<style>
|
||||
html,body{height:100%;margin:0;padding:0;}
|
||||
@@ -263,7 +263,7 @@ function mobToggle(which){
|
||||
}
|
||||
|
||||
// ── Theme ─────────────────────────────────────────────────────────────────────
|
||||
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('cherryclaw_theme',n);}catch{}}
|
||||
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('homeclaw_theme',n);}catch{}}
|
||||
|
||||
// ── Resize ────────────────────────────────────────────────────────────────────
|
||||
(function(){
|
||||
|
||||
+14
-14
@@ -8,9 +8,9 @@ let pollInterval = null;
|
||||
let currentMode = 'chat';
|
||||
let sidebarTab = 'jobs';
|
||||
let useAgentMode = false;
|
||||
const CHAT_SESSIONS_KEY_BASE = 'cherryclaw_chat_sessions_v1';
|
||||
const AGENT_SESSION_KEY_BASE = 'cherryclaw_agent_session_id';
|
||||
const THEME_KEY = 'cherryclaw_theme';
|
||||
const CHAT_SESSIONS_KEY_BASE = 'homeclaw_chat_sessions_v1';
|
||||
const AGENT_SESSION_KEY_BASE = 'homeclaw_agent_session_id';
|
||||
const THEME_KEY = 'homeclaw_theme';
|
||||
const TOKEN_KEY = 'smallclaw_token';
|
||||
function getAuthToken() { try { return sessionStorage.getItem(TOKEN_KEY) || ''; } catch { return ''; } }
|
||||
function setAuthToken(t) { try { t ? sessionStorage.setItem(TOKEN_KEY, t) : sessionStorage.removeItem(TOKEN_KEY); t ? localStorage.setItem(TOKEN_KEY, t) : localStorage.removeItem(TOKEN_KEY); } catch {} }
|
||||
@@ -262,7 +262,7 @@ let liveTaskSteps = [];
|
||||
let quickSearchRigor = 'verified';
|
||||
let _searchSettingsLoaded = false; // true after openSettings() successfully loads search config
|
||||
let _modelSettingsLoaded = false; // true after loadModelSettings() successfully populates model/provider DOM
|
||||
let quickThinkingEffort = localStorage.getItem('cherryclaw_quick_thinking_effort') || 'standard';
|
||||
let quickThinkingEffort = localStorage.getItem('homeclaw_quick_thinking_effort') || 'standard';
|
||||
let queuedPrompts = [];
|
||||
const MAX_QUEUED_PROMPTS = 8;
|
||||
let currentPreflightStatus = '';
|
||||
@@ -1102,8 +1102,8 @@ function renderChatMessages() {
|
||||
if (chatHistory.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="chat-welcome" id="chat-welcome">
|
||||
<div class="chat-welcome-icon"><img src="cherry_logo.png" alt="🍒"></div>
|
||||
<h2>CherryClaw</h2>
|
||||
<div class="chat-welcome-icon"><img src="homeclaw_logo.png" alt="🏠"></div>
|
||||
<h2>HomeClaw</h2>
|
||||
<p>Chat directly with your local model. No API keys, no cloud — just you and the model.</p>
|
||||
<div class="hint">Enable Agent mode when you want planning + tool execution in this chat.</div>
|
||||
</div>`;
|
||||
@@ -1121,9 +1121,9 @@ function renderChatMessages() {
|
||||
}
|
||||
return `
|
||||
<div class="msg ${msg.role}${pinClass}${selectedClass}${confirmedClass}"${clickHandler}>
|
||||
<div class="msg-avatar">${msg.role === 'user' ? 'U' : '<img src="cherry_logo.png" style="width:48px;height:48px;border-radius:6px">'}</div>
|
||||
<div class="msg-avatar">${msg.role === 'user' ? 'U' : '<img src="homeclaw_logo.png" style="width:48px;height:48px;border-radius:6px">'}</div>
|
||||
<div class="msg-body">
|
||||
<div class="msg-role">${msg.role === 'user' ? 'You' : (msg.mode === 'agentic' ? 'CherryClaw (Agent)' : 'CherryClaw')}${msg.role === 'ai' ? ` <button class="tts-btn" onclick="playTTSMsg(this,${idx})" title="음성으로 듣기">🔊</button>` : ''}</div>
|
||||
<div class="msg-role">${msg.role === 'user' ? 'You' : (msg.mode === 'agentic' ? 'HomeClaw (Agent)' : 'HomeClaw')}${msg.role === 'ai' ? ` <button class="tts-btn" onclick="playTTSMsg(this,${idx})" title="음성으로 듣기">🔊</button>` : ''}</div>
|
||||
${msg.role === 'ai' ? renderAssistantContent(msg.content) : `<div class="msg-content">${renderMarkdown(msg.display !== undefined ? msg.display : msg.content, { hideDownload: true })}</div>`}
|
||||
${msg.role === 'ai' ? renderArtifacts(msg.artifacts) : ''}
|
||||
${msg.role === 'ai' ? renderFileDownloads(msg.steps, msg.processEntries, msg.content) : ''}
|
||||
@@ -1146,9 +1146,9 @@ function renderChatMessages() {
|
||||
: '';
|
||||
container.innerHTML += `
|
||||
<div class="msg ai" id="thinking-msg">
|
||||
<div class="msg-avatar"><img src="cherry_logo.png" style="width:48px;height:48px;border-radius:6px"></div>
|
||||
<div class="msg-avatar"><img src="homeclaw_logo.png" style="width:48px;height:48px;border-radius:6px"></div>
|
||||
<div class="msg-body">
|
||||
<div class="msg-role">CherryClaw${useAgentMode ? ' (Agent)' : ''}</div>
|
||||
<div class="msg-role">HomeClaw${useAgentMode ? ' (Agent)' : ''}</div>
|
||||
${currentPreflightStatus ? `<div class="msg-content" style="margin-bottom:6px;color:#26487e">${escHtml(currentPreflightStatus)}</div>` : ''}
|
||||
${progressHtml}
|
||||
<div class="thinking" style="align-items:center">
|
||||
@@ -2483,7 +2483,7 @@ async function api(path, opts = {}) {
|
||||
return data;
|
||||
}
|
||||
|
||||
const LAST_USER_KEY = 'cherryclaw_last_user';
|
||||
const LAST_USER_KEY = 'homeclaw_last_user';
|
||||
|
||||
function clearLocalUserState(reason) {
|
||||
try {
|
||||
@@ -2636,7 +2636,7 @@ async function setQuickSearchRigor(level) {
|
||||
|
||||
function setQuickThinkingEffort(level) {
|
||||
quickThinkingEffort = level === 'extended' ? 'extended' : 'standard';
|
||||
localStorage.setItem('cherryclaw_quick_thinking_effort', quickThinkingEffort);
|
||||
localStorage.setItem('homeclaw_quick_thinking_effort', quickThinkingEffort);
|
||||
updateQuickModeUI();
|
||||
addProcessEntry('info', `Thinking effort set to ${quickThinkingEffort} (UI preference).`);
|
||||
}
|
||||
@@ -8434,7 +8434,7 @@ function renderBgTasks() {
|
||||
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">작업</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 style="font-size:12px">Ask HomeClaw to do something in the background and it'll appear here.</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
@@ -8866,7 +8866,7 @@ function copyWebhookCurl() {
|
||||
const origin = window.location.origin || 'http://localhost:18789';
|
||||
const hookPath = document.getElementById('wh-path')?.value?.trim() || '/hooks';
|
||||
const curl = `curl -X POST ${origin}${hookPath}/agent \\
|
||||
-H "x-cherryclaw-token: YOUR_TOKEN" \\
|
||||
-H "X-Gateway-Token: YOUR_TOKEN" \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{"message": "Hello from webhook", "name": "MyApp", "deliver": true}'`;
|
||||
navigator.clipboard?.writeText(curl).then(() => {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.2 KiB |
+2
-2
@@ -12,7 +12,7 @@
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nanum+Gothic:wght@400;700;800&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=JetBrains+Mono:wght@400;500;600&family=Fira+Code:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="homeclaw_logo.png">
|
||||
<!-- CodeMirror + xterm (UMD libs loaded before Monaco's AMD loader to avoid anonymous define() clashes) -->
|
||||
<link rel="stylesheet" href="/vendor/codemirror/lib/codemirror.css">
|
||||
<script src="/vendor/codemirror/lib/codemirror.js"></script>
|
||||
@@ -25,7 +25,7 @@
|
||||
<script src="/vendor/vs/loader.js"></script>
|
||||
<script>
|
||||
try {
|
||||
var _t = localStorage.getItem('code-page-theme') || localStorage.getItem('cherryclaw_theme') || 'dark';
|
||||
var _t = localStorage.getItem('code-page-theme') || localStorage.getItem('homeclaw_theme') || 'dark';
|
||||
document.documentElement.setAttribute('data-theme', _t);
|
||||
} catch (e) {}
|
||||
</script>
|
||||
|
||||
+1
-1
@@ -6037,7 +6037,7 @@ function setCodePageTheme(name) {
|
||||
}
|
||||
}
|
||||
(function() {
|
||||
const t = localStorage.getItem('code-page-theme') || localStorage.getItem('cherryclaw_theme') || 'dark';
|
||||
const t = localStorage.getItem('code-page-theme') || localStorage.getItem('homeclaw_theme') || 'dark';
|
||||
setCodePageTheme(t);
|
||||
})();
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>🔍 탐정 어시스턴트</title>
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="homeclaw_logo.png">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script>try{var _t=localStorage.getItem('cherryclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
|
||||
<script>try{var _t=localStorage.getItem('homeclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
|
||||
<style>
|
||||
html,body{height:100%;margin:0;padding:0;}
|
||||
body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;display:flex;flex-direction:column;overflow:hidden;height:100dvh;}
|
||||
@@ -674,7 +674,7 @@ async function checkAuth(){
|
||||
}
|
||||
|
||||
// ── Theme ─────────────────────────────────────────────────────────────────────
|
||||
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('cherryclaw_theme',n);}catch{}}
|
||||
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('homeclaw_theme',n);}catch{}}
|
||||
|
||||
// ── Mobile ────────────────────────────────────────────────────────────────────
|
||||
function mobCloseAll(){
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
+12
-12
@@ -3,12 +3,12 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>CherryClaw</title>
|
||||
<title>HomeClaw</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nanum+Gothic:wght@400;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
|
||||
<link rel="apple-touch-icon" sizes="128x128" href="cherry_logo.png">
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="homeclaw_logo.png">
|
||||
<link rel="apple-touch-icon" sizes="128x128" href="homeclaw_logo.png">
|
||||
<link rel="stylesheet" href="/vendor/codemirror/lib/codemirror.css">
|
||||
<script src="/vendor/codemirror/lib/codemirror.js"></script>
|
||||
<script src="/vendor/codemirror/mode/xml/xml.js"></script>
|
||||
@@ -55,7 +55,7 @@
|
||||
<!-- HEADER -->
|
||||
<header>
|
||||
<div class="logo">
|
||||
<img src="cherry_logo.png" alt="🍒"> CherryClaw
|
||||
<img src="homeclaw_logo.png" alt="🏠"> HomeClaw
|
||||
</div>
|
||||
|
||||
<div class="mode-toggle" style="margin-left:8px">
|
||||
@@ -310,8 +310,8 @@
|
||||
<div id="chat-view">
|
||||
<div id="chat-messages">
|
||||
<div class="chat-welcome" id="chat-welcome">
|
||||
<div class="chat-welcome-icon"><img src="cherry_logo.png" alt="🍒"></div>
|
||||
<h2>CherryClaw</h2>
|
||||
<div class="chat-welcome-icon"><img src="homeclaw_logo.png" alt="🏠"></div>
|
||||
<h2>HomeClaw</h2>
|
||||
<p>로컬 모델과 직접 대화하세요. API 키도, 클라우드도 없이 — 오직 나와 모델만.</p>
|
||||
<div class="hint">멀티스텝 에이전트 작업은 에이전트 모드로 전환하세요.</div>
|
||||
</div>
|
||||
@@ -422,7 +422,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="chat-hint">
|
||||
<span id="chat-model-name">모델</span>과 대화 중 — <span id="chat-provider-name">CherryClaw</span> 경유.
|
||||
<span id="chat-model-name">모델</span>과 대화 중 — <span id="chat-provider-name">HomeClaw</span> 경유.
|
||||
<span id="queued-prompts-indicator" style="display:none;margin-left:8px;color:var(--brand);font-weight:700"></span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -969,7 +969,7 @@
|
||||
<div style="border:1px solid var(--line);border-radius:12px;padding:12px">
|
||||
<div class="right-section-title" style="margin-bottom:8px">파일 접근</div>
|
||||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:6px">작업공간 경로</label>
|
||||
<input id="settings-workspace-path" type="text" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-family:'IBM Plex Mono',monospace;font-size:12px;margin-bottom:10px" placeholder="예: D:\CherryClaw\workspace" />
|
||||
<input id="settings-workspace-path" type="text" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-family:'IBM Plex Mono',monospace;font-size:12px;margin-bottom:10px" placeholder="예: D:\HomeClaw\workspace" />
|
||||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:6px">허용 경로 (한 줄에 하나)</label>
|
||||
<textarea id="settings-allowed-paths" style="width:100%;min-height:120px;border:1px solid var(--line);border-radius:10px;padding:8px;font-family:'IBM Plex Mono',monospace;font-size:12px"></textarea>
|
||||
<label style="display:block;font-size:12px;color:var(--muted);margin:10px 0 6px">차단 경로 (한 줄에 하나)</label>
|
||||
@@ -1110,7 +1110,7 @@
|
||||
<div id="prov-fields-openai_codex" style="display:none">
|
||||
<div class="right-section-title" style="margin-bottom:8px">OpenAI — GPT Plus / Codex</div>
|
||||
<div id="codex-disconnected-state">
|
||||
<div style="font-size:12px;color:var(--muted);margin-bottom:12px;line-height:1.6">OAuth로 ChatGPT Plus 또는 Pro 계정을 연결하세요. 브라우저에서 CherryClaw 인증이 열립니다.</div>
|
||||
<div style="font-size:12px;color:var(--muted);margin-bottom:12px;line-height:1.6">OAuth로 ChatGPT Plus 또는 Pro 계정을 연결하세요. 브라우저에서 HomeClaw 인증이 열립니다.</div>
|
||||
<button class="btn" onclick="startCodexOAuth()" style="width:100%;background:var(--brand);color:#fff;border:none;padding:10px;border-radius:10px;font-size:13px;cursor:pointer">ChatGPT 계정 연결</button>
|
||||
<div id="codex-oauth-status" style="font-size:11px;color:var(--muted);margin-top:8px;text-align:center"></div>
|
||||
</div>
|
||||
@@ -1624,7 +1624,7 @@
|
||||
<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">워크스페이스</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" />
|
||||
<input id="agent-edit-workspace" type="text" placeholder="D:/HomeClaw/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>
|
||||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:4px">Model</label>
|
||||
@@ -1791,7 +1791,7 @@
|
||||
<code style="background:#f4f4f4;padding:2px 5px;border-radius:4px">GET /hooks/status</code> — health check<br><br>
|
||||
<b>Auth:</b> Send token as<br>
|
||||
<code style="background:#f4f4f4;padding:2px 5px;border-radius:4px">Authorization: Bearer <token></code><br>
|
||||
or <code style="background:#f4f4f4;padding:2px 5px;border-radius:4px">x-cherryclaw-token: <token></code><br><br>
|
||||
or <code style="background:#f4f4f4;padding:2px 5px;border-radius:4px">X-Gateway-Token: <token></code><br><br>
|
||||
Works with: GitHub Actions, Stripe, Vercel, n8n, IFTTT, Home Assistant, or any script.
|
||||
</div>
|
||||
</div>
|
||||
@@ -1867,7 +1867,7 @@
|
||||
<div class="right-section-title" style="margin-bottom:8px">What is MCP?</div>
|
||||
<div style="font-size:12px;color:var(--muted);line-height:1.85">
|
||||
<b>Model Context Protocol</b> (by Anthropic) is a standard that lets AI connect to external tools and data sources.<br><br>
|
||||
Each MCP server exposes a set of <b>tools</b> — CherryClaw discovers them automatically when you connect.
|
||||
Each MCP server exposes a set of <b>tools</b> — HomeClaw discovers them automatically when you connect.
|
||||
The AI can then call them during tasks, just like its built-in tools.<br><br>
|
||||
Servers run <b>locally as child processes</b> (stdio) or connect to <b>remote HTTP endpoints</b> (SSE).
|
||||
No data leaves your machine unless the tool itself calls an external API.
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>📈 투자 어시스턴트</title>
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="homeclaw_logo.png">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script>try{var _t=localStorage.getItem('cherryclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
|
||||
<script>try{var _t=localStorage.getItem('homeclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
|
||||
<style>
|
||||
html,body{height:100%;margin:0;padding:0;}
|
||||
body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;display:flex;flex-direction:column;overflow:hidden;height:100dvh;}
|
||||
@@ -478,7 +478,7 @@ async function checkAuth(){
|
||||
}
|
||||
|
||||
// ── Theme ─────────────────────────────────────────────────────────────────────
|
||||
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('cherryclaw_theme',n);}catch{}}
|
||||
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('homeclaw_theme',n);}catch{}}
|
||||
|
||||
// ── Mobile overlay ────────────────────────────────────────────────────────────
|
||||
function mobCloseAll(){
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
<title id="doc-title">🇺🇿 우즈베크어 학습</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nanum+Gothic:wght@400;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="homeclaw_logo.png">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script>
|
||||
try {
|
||||
var _t = localStorage.getItem('cherryclaw_theme') || 'dark';
|
||||
var _t = localStorage.getItem('homeclaw_theme') || 'dark';
|
||||
document.documentElement.setAttribute('data-theme', _t);
|
||||
} catch(e) {}
|
||||
</script>
|
||||
@@ -1079,7 +1079,7 @@ function saveProgress() {
|
||||
// ═══════════════════════════════════════════════════════
|
||||
function setTheme(t) {
|
||||
document.documentElement.setAttribute('data-theme', t);
|
||||
localStorage.setItem('cherryclaw_theme', t);
|
||||
localStorage.setItem('homeclaw_theme', t);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>⚖️ 법률 어시스턴트</title>
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="homeclaw_logo.png">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script>try{var _t=localStorage.getItem('cherryclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
|
||||
<script>try{var _t=localStorage.getItem('homeclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
|
||||
<style>
|
||||
html,body{height:100%;margin:0;padding:0;}
|
||||
body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;display:flex;flex-direction:column;overflow:hidden;height:100dvh;}
|
||||
@@ -295,7 +295,7 @@ async function checkAuth(){
|
||||
}
|
||||
|
||||
// ── Theme ─────────────────────────────────────────────────────────────────────
|
||||
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('cherryclaw_theme',n);}catch{}}
|
||||
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('homeclaw_theme',n);}catch{}}
|
||||
|
||||
// ── Mobile overlay ────────────────────────────────────────────────────────────
|
||||
function mobCloseAll(){
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>🧠 마음 상담실</title>
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="homeclaw_logo.png">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script>try{var _t=localStorage.getItem('cherryclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
|
||||
<script>try{var _t=localStorage.getItem('homeclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
|
||||
<style>
|
||||
html,body{height:100%;margin:0;padding:0;}
|
||||
body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;display:flex;flex-direction:column;overflow:hidden;height:100dvh;}
|
||||
@@ -426,7 +426,7 @@ function mobToggle(which){
|
||||
}
|
||||
|
||||
// ── Theme ─────────────────────────────────────────────────────────────────────
|
||||
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('cherryclaw_theme',n);}catch{}}
|
||||
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('homeclaw_theme',n);}catch{}}
|
||||
|
||||
// ── Resize ────────────────────────────────────────────────────────────────────
|
||||
(function(){
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>🎵 음악 스튜디오</title>
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="homeclaw_logo.png">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/@coderline/alphatab@latest/dist/alphaTab.min.js"></script>
|
||||
<script>
|
||||
try { var _t=localStorage.getItem('cherryclaw_music_theme')||'dark'; document.documentElement.setAttribute('data-theme',_t); } catch(e){}
|
||||
try { var _t=localStorage.getItem('homeclaw_music_theme')||'dark'; document.documentElement.setAttribute('data-theme',_t); } catch(e){}
|
||||
</script>
|
||||
<style>
|
||||
/* ── Premium studio theme tokens ── */
|
||||
@@ -1170,7 +1170,7 @@ function toggleTheme() {
|
||||
const d=document.documentElement;
|
||||
const n=d.getAttribute('data-theme')==='dark'?'light':'dark';
|
||||
d.setAttribute('data-theme',n);
|
||||
try{localStorage.setItem('cherryclaw_music_theme',n);}catch{}
|
||||
try{localStorage.setItem('homeclaw_music_theme',n);}catch{}
|
||||
}
|
||||
|
||||
// ── Resize handle ─────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>🎨 이미지·동영상 스튜디오</title>
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="homeclaw_logo.png">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script>try{var _t=localStorage.getItem('cherryclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
|
||||
<script>try{var _t=localStorage.getItem('homeclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
|
||||
<style>
|
||||
:root{--brand:#a78bfa;}
|
||||
html,body{height:100%;margin:0;padding:0;}
|
||||
@@ -328,7 +328,7 @@ async function checkAuth(){
|
||||
try{const r=await fetch('/api/auth/status',{headers:authH()});if(!r.ok){location.href='/login.html?redirect='+encodeURIComponent(location.pathname);return false;}const d=await r.json();if(!d.authenticated){location.href='/login.html?redirect='+encodeURIComponent(location.pathname);return false;}return true;}
|
||||
catch{location.href='/login.html?redirect='+encodeURIComponent(location.pathname);return false;}
|
||||
}
|
||||
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('cherryclaw_theme',n);}catch{}}
|
||||
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('homeclaw_theme',n);}catch{}}
|
||||
|
||||
// ── Selection / bulk-delete helpers (shared by the gallery grid and both saved-photo pickers) ──
|
||||
let uploadsSelected1=new Set(), uploadsSelected2=new Set(), gallerySelected=new Set();
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>🚦 교통 CCTV</title>
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="homeclaw_logo.png">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script>try{var _t=localStorage.getItem('cherryclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
|
||||
<script>try{var _t=localStorage.getItem('homeclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
|
||||
<style>
|
||||
:root{--brand:#f59e0b;}
|
||||
html,body{height:100%;margin:0;padding:0;}
|
||||
@@ -194,7 +194,7 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis
|
||||
const TOKEN_KEY='smallclaw_token';
|
||||
function getToken(){try{return sessionStorage.getItem(TOKEN_KEY)||localStorage.getItem(TOKEN_KEY)||'';}catch{return '';}}
|
||||
function authH(extra){const t=getToken();const h={...(extra||{})};if(t)h['Authorization']='Bearer '+t;return h;}
|
||||
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('cherryclaw_theme',n);}catch{}}
|
||||
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('homeclaw_theme',n);}catch{}}
|
||||
|
||||
let allItems=[];
|
||||
let currentUrl=null;
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>🌤️ 날씨 어시스턴트</title>
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="cherry_logo.png">
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="homeclaw_logo.png">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script>try{var _t=localStorage.getItem('cherryclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
|
||||
<script>try{var _t=localStorage.getItem('homeclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
|
||||
<style>
|
||||
:root{--brand:#38bdf8;}
|
||||
html,body{height:100%;margin:0;padding:0;}
|
||||
@@ -295,7 +295,7 @@ async function checkAuth(){
|
||||
}
|
||||
|
||||
// ── Theme ─────────────────────────────────────────────────────────────────────
|
||||
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('cherryclaw_theme',n);}catch{}}
|
||||
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('homeclaw_theme',n);}catch{}}
|
||||
|
||||
// ── Resize ────────────────────────────────────────────────────────────────────
|
||||
(function(){
|
||||
|
||||
Reference in New Issue
Block a user