Merge pull request #1009 from normal-coder/feat-ux-optimize

Refactor UI navigation and improve visual consistency with SVGs
This commit is contained in:
Jaber Jaber
2026-04-10 20:01:16 +03:00
committed by GitHub
5 changed files with 124 additions and 99 deletions
+11 -11
View File
@@ -23,7 +23,7 @@
.btn:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }
.btn-primary {
background: var(--accent);
color: var(--bg-primary);
color: var(--text-on-accent);
box-shadow: var(--shadow-xs), var(--shadow-inset);
}
.btn-primary:hover { background: var(--accent-dim); box-shadow: var(--shadow-sm), var(--shadow-accent); transform: translateY(-1px); }
@@ -538,7 +538,7 @@ tr:hover td { background: var(--surface2); }
height: 14px;
border-radius: 50%;
background: var(--accent);
color: var(--bg-primary);
color: var(--text-on-accent);
font-size: 9px;
font-weight: 700;
display: flex;
@@ -864,7 +864,7 @@ mark.search-highlight {
border-radius: 50%;
border: none;
background: var(--accent);
color: var(--bg-primary);
color: var(--text-on-accent);
cursor: pointer;
display: flex;
align-items: center;
@@ -1994,7 +1994,7 @@ mark.search-highlight {
}
.filter-pill:hover { border-color: var(--accent); color: var(--text); }
.filter-pill.active { background: var(--accent); color: var(--bg-primary); border-color: var(--accent); }
.filter-pill.active { background: var(--accent); color: var(--text-on-accent); border-color: var(--accent); }
/* ── Difficulty badges ── */
.difficulty-badge {
@@ -2258,7 +2258,7 @@ mark.search-highlight {
.wizard-progress-step.wiz-active .wizard-progress-circle {
border-color: var(--accent);
background: var(--accent);
color: var(--bg-primary);
color: var(--text-on-accent);
box-shadow: 0 0 0 4px var(--accent-glow);
}
@@ -2485,7 +2485,7 @@ mark.search-highlight {
/* ── Try-It Mini Chat ── */
.tryit-messages { max-height: 200px; overflow-y: auto; margin: 12px 0; }
.tryit-msg { padding: 6px 10px; border-radius: 6px; margin: 4px 0; font-size: 12px; line-height: 1.5; word-break: break-word; }
.tryit-msg-user { background: var(--accent); color: var(--bg-primary); margin-left: 40px; }
.tryit-msg-user { background: var(--accent); color: var(--text-on-accent); margin-left: 40px; }
.tryit-msg-agent { background: var(--surface2); margin-right: 40px; }
/* ── Suggested Message Chips ── */
@@ -2503,7 +2503,7 @@ mark.search-highlight {
.channel-steps { display: flex; align-items: center; gap: 0; margin-bottom: 20px; }
.channel-step-item { display: flex; align-items: center; gap: 6px; flex: 1; }
.channel-step-num { width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 700; border: 2px solid var(--border); color: var(--text-dim); flex-shrink: 0; transition: all 0.2s; }
.channel-step-num.active { border-color: var(--accent); background: var(--accent); color: var(--bg-primary); }
.channel-step-num.active { border-color: var(--accent); background: var(--accent); color: var(--text-on-accent); }
.channel-step-num.done { border-color: var(--success); background: var(--success); color: #000; }
.channel-step-label { font-size: 11px; color: var(--text-dim); }
.channel-step-label.active { color: var(--accent); font-weight: 600; }
@@ -2520,7 +2520,7 @@ mark.search-highlight {
.wizard-category-pills { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 16px; }
.wizard-category-pill { padding: 4px 12px; border-radius: 20px; font-size: 11px; font-weight: 600; cursor: pointer; border: 1px solid var(--border); background: transparent; color: var(--text-dim); transition: all 0.15s; font-family: var(--font-mono); }
.wizard-category-pill:hover { border-color: var(--accent); color: var(--text); }
.wizard-category-pill.active { background: var(--accent); color: var(--bg-primary); border-color: var(--accent); }
.wizard-category-pill.active { background: var(--accent); color: var(--text-on-accent); border-color: var(--accent); }
/* ── Capability Preview Panel ── */
.capability-preview { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-md); padding: 12px; margin-top: 12px; }
@@ -2626,7 +2626,7 @@ mark.search-highlight {
.personality-pill.active {
border-color: var(--accent);
background: var(--accent);
color: var(--bg-primary);
color: var(--text-on-accent);
box-shadow: 0 0 12px var(--accent-subtle);
}
@@ -2693,7 +2693,7 @@ mark.search-highlight {
justify-content: space-between;
}
.nav-section-chevron {
font-size: 8px;
font-size: 16px;
transition: transform var(--transition-fast);
color: var(--text-muted);
}
@@ -2799,7 +2799,7 @@ mark.search-highlight {
.hand-step-item.active .hand-step-num {
border-color: var(--accent);
background: var(--accent);
color: var(--bg-primary);
color: var(--text-on-accent);
}
.hand-step-item.done .hand-step-num {
border-color: var(--success);
+3 -3
View File
@@ -120,11 +120,11 @@
}
.nav-section-title {
font-size: 9px;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 1.5px;
color: var(--text-muted);
padding: 12px 12px 4px;
padding: 12px 12px 6px 3px;
font-weight: 600;
}
@@ -154,7 +154,7 @@
.nav-item.active {
background: var(--accent);
color: var(--bg-primary);
color: var(--text-on-accent);
font-weight: 600;
box-shadow: var(--shadow-sm), 0 2px 8px rgba(255, 92, 0, 0.2);
}
+11 -9
View File
@@ -50,6 +50,7 @@
/* Chat-specific */
--agent-bg: #F5F4F2;
--user-bg: #FFF3E6;
--text-on-accent: #FFFFFF;
/* Layout */
--sidebar-width: 240px;
@@ -91,16 +92,17 @@
--bg: #080706;
--bg-primary: #0F0E0E;
--bg-elevated: #161413;
--surface: #1F1D1C;
--surface2: #2A2725;
--surface: #242221;
--surface2: #2F2D2C;
--surface3: #1A1817;
--border: #2D2A28;
--border-light: #3D3A38;
--border-subtle: #232120;
--text: #F0EFEE;
--text-secondary: #C4C0BC;
--text-dim: #8A8380;
--text-muted: #5C5754;
--border: #363230;
--border-light: #4A4644;
--border-subtle: #2D2A28;
--text: #FFFFFF;
--text-secondary: #D1D1D1;
--text-dim: #9FA0A0;
--text-muted: #6B6663;
--text-on-accent: #FFFFFF;
--accent: #FF5C00;
--accent-light: #FF7A2E;
--accent-dim: #E05200;
+98 -74
View File
@@ -56,45 +56,11 @@
</div>
<div class="sidebar-nav" role="navigation" aria-label="Main navigation">
<!-- Chat — primary action -->
<div class="nav-section" x-data="{ collapsed: false }" aria-label="Chat">
<div class="nav-section-title" @click="collapsed = !collapsed">
<span class="nav-label">Chat</span>
<span class="nav-section-chevron" :style="collapsed ? '' : 'transform:rotate(90deg)'">&rsaquo;</span>
</div>
<template x-if="!collapsed">
<div x-transition>
<a class="nav-item" :class="{ active: page === 'agents' }" @click="navigate('agents')" :aria-current="page === 'agents' ? 'page' : false">
<span class="nav-icon"><svg viewBox="0 0 24 24"><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></span>
<span class="nav-label">Chat</span>
</a>
</div>
</template>
</div>
<!-- Monitor -->
<div class="nav-section" x-data="{ collapsed: false }" aria-label="Monitor">
<div class="nav-section-title" @click="collapsed = !collapsed">
<span class="nav-label">Monitor</span>
<span class="nav-section-chevron" :style="collapsed ? '' : 'transform:rotate(90deg)'">&rsaquo;</span>
</div>
<template x-if="!collapsed">
<div x-transition>
<a class="nav-item" :class="{ active: page === 'overview' }" @click="navigate('overview')" :aria-current="page === 'overview' ? 'page' : false">
<!-- Overview — primary action as first screen -->
<a class="nav-item" :class="{ active: page === 'overview' }" @click="navigate('overview')" :aria-current="page === 'overview' ? 'page' : false" style="margin-bottom: 4px;">
<span class="nav-icon"><svg viewBox="0 0 24 24"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M9 22V12h6v10"/></svg></span>
<span class="nav-label">Overview</span>
</a>
<a class="nav-item" :class="{ active: page === 'analytics' }" @click="navigate('analytics')" :aria-current="page === 'analytics' ? 'page' : false">
<span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M18 20V10M12 20V4M6 20v-6"/></svg></span>
<span class="nav-label">Analytics</span>
</a>
<a class="nav-item" :class="{ active: page === 'logs' }" @click="navigate('logs')" :aria-current="page === 'logs' ? 'page' : false">
<span class="nav-icon"><svg viewBox="0 0 24 24"><path d="m4 17 6-6-6-6"/><path d="M12 19h8"/></svg></span>
<span class="nav-label">Logs</span>
</a>
</div>
</template>
</div>
<!-- Agents -->
<div class="nav-section" x-data="{ collapsed: false }" aria-label="Agents">
@@ -104,6 +70,10 @@
</div>
<template x-if="!collapsed">
<div x-transition>
<a class="nav-item" :class="{ active: page === 'agents' }" @click="navigate('agents')" :aria-current="page === 'agents' ? 'page' : false">
<span class="nav-icon"><svg viewBox="0 0 24 24"><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></span>
<span class="nav-label">Chat</span>
</a>
<a class="nav-item" :class="{ active: page === 'sessions' }" @click="navigate('sessions')" :aria-current="page === 'sessions' ? 'page' : false">
<span class="nav-icon"><svg viewBox="0 0 24 24"><path d="m12 2-10 5 10 5 10-5z"/><path d="m2 17 10 5 10-5"/><path d="m2 12 10 5 10-5"/></svg></span>
<span class="nav-label">Sessions</span>
@@ -165,6 +135,26 @@
</template>
</div>
<!-- Monitor -->
<div class="nav-section" x-data="{ collapsed: false }" aria-label="Monitor">
<div class="nav-section-title" @click="collapsed = !collapsed">
<span class="nav-label">Monitor</span>
<span class="nav-section-chevron" :style="collapsed ? '' : 'transform:rotate(90deg)'">&rsaquo;</span>
</div>
<template x-if="!collapsed">
<div x-transition>
<a class="nav-item" :class="{ active: page === 'analytics' }" @click="navigate('analytics')" :aria-current="page === 'analytics' ? 'page' : false">
<span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M18 20V10M12 20V4M6 20v-6"/></svg></span>
<span class="nav-label">Analytics</span>
</a>
<a class="nav-item" :class="{ active: page === 'logs' }" @click="navigate('logs')" :aria-current="page === 'logs' ? 'page' : false">
<span class="nav-icon"><svg viewBox="0 0 24 24"><path d="m4 17 6-6-6-6"/><path d="M12 19h8"/></svg></span>
<span class="nav-label">Logs</span>
</a>
</div>
</template>
</div>
<!-- System -->
<div class="nav-section" x-data="{ collapsed: false }" aria-label="System">
<div class="nav-section-title" @click="collapsed = !collapsed">
@@ -257,8 +247,10 @@
<template x-for="item in setupChecklist" :key="item.key">
<div class="setup-checklist-item">
<div class="setup-checklist-icon" :class="{ done: item.done }">
<span x-show="item.done">&#10003;</span>
<span x-show="!item.done">&#9675;</span>
<span x-show="item.done">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" style="display:block"><polyline points="20 6 9 17 4 12"/></svg>
</span>
<span x-show="!item.done" style="width:10px;height:10px;border-radius:50%;border:1.5px solid var(--border);display:block"></span>
</div>
<span style="flex:1" :style="item.done ? 'text-decoration:line-through;opacity:0.6' : ''" x-text="item.label"></span>
<a x-show="!item.done" :href="item.action" class="btn btn-ghost btn-sm" style="font-size:10px;padding:3px 8px">Go</a>
@@ -869,7 +861,7 @@
</div>
<span class="badge" :class="'badge-' + agent.state.toLowerCase()" x-text="agent.state" style="font-size:10px"></span>
<button class="agent-chip-config-btn" @click.stop="showDetail(agent)" title="Agent settings" style="display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:50%;border:1px solid var(--border);background:transparent;cursor:pointer;color:var(--text-dim);transition:all 0.15s;flex-shrink:0" @mouseenter="$el.style.borderColor='var(--accent)';$el.style.color='var(--accent)';$el.style.background='var(--surface2)'" @mouseleave="$el.style.borderColor='var(--border)';$el.style.color='var(--text-dim)';$el.style.background='transparent'">
<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 0 2.83 2 2 0 0 1-2.83 0l-.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-2 2 2 2 0 0 1-2-2v-.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 0 2 2 0 0 1 0-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-2-2 2 2 0 0 1 2-2h.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 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.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 0 2 2 0 0 1 0 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 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
<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 0 0 1 2-2 2 2 0 0 1 2 2v.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 0 2 2 0 0 1 0 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 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
</button>
</div>
</template>
@@ -904,7 +896,9 @@
<span x-show="detailAgent.identity && detailAgent.identity.emoji" x-text="detailAgent.identity && detailAgent.identity.emoji" style="margin-right:6px"></span>
<span x-text="detailAgent.name"></span>
</h3>
<button class="modal-close" @click="showDetailModal = false">&times;</button>
<button class="modal-close" @click="showDetailModal = false">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
</div>
<div class="tabs" style="margin-bottom:16px">
<div class="tab" :class="{ active: detailTab === 'info' }" @click="detailTab = 'info'">Info</div>
@@ -1413,7 +1407,7 @@
<option value="conditional">Conditional</option>
<option value="loop">Loop</option>
</select>
<button class="btn btn-danger btn-sm" @click="newWf.steps.splice(i,1)">&times;</button>
<button class="btn btn-danger btn-sm" @click="newWf.steps.splice(i,1)"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg></button>
</div>
<input class="form-input mt-2" x-model="step.prompt" placeholder="Prompt template (use {{input}})">
</div>
@@ -1428,7 +1422,7 @@
<template x-if="runModal">
<div class="modal-overlay" @click.self="runModal = null" @keydown.escape.window="runModal = null">
<div class="modal">
<div class="modal-header"><h3 x-text="'Run: ' + runModal.name"></h3><button class="modal-close" @click="runModal = null">&times;</button></div>
<div class="modal-header"><h3 x-text="'Run: ' + runModal.name"></h3><button class="modal-close" @click="runModal = null"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg></button></div>
<div class="form-group"><label>Input</label><textarea class="form-textarea" x-model="runInput" placeholder="Enter workflow input..."></textarea></div>
<button class="btn btn-primary btn-block" @click="executeWorkflow()" :disabled="running">
<span x-show="!running">Execute</span><span x-show="running">Running...</span>
@@ -1444,7 +1438,7 @@
<template x-if="editModal">
<div class="modal-overlay" @click.self="editModal = null" @keydown.escape.window="editModal = null">
<div class="modal">
<div class="modal-header"><h3 x-text="'Edit: ' + editModal.name"></h3><button class="modal-close" @click="editModal = null">&times;</button></div>
<div class="modal-header"><h3 x-text="'Edit: ' + editModal.name"></h3><button class="modal-close" @click="editModal = null"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg></button></div>
<div class="form-group"><label>Name</label><input class="form-input" x-model="editWf.name" placeholder="Workflow name"></div>
<div class="form-group"><label>Description</label><input class="form-input" x-model="editWf.description" placeholder="What does this workflow do?"></div>
<div class="mb-4">
@@ -1462,7 +1456,7 @@
<option value="conditional">Conditional</option>
<option value="loop">Loop</option>
</select>
<button class="btn btn-danger btn-sm" @click="editWf.steps.splice(i,1)">&times;</button>
<button class="btn btn-danger btn-sm" @click="editWf.steps.splice(i,1)"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg></button>
</div>
<input class="form-input mt-2" x-model="step.prompt" placeholder="Prompt template (use {{input}})">
</div>
@@ -1550,7 +1544,7 @@
<div class="wf-editor-panel" x-show="showNodeEditor && selectedNode" x-transition>
<div class="wf-editor-header">
<span class="font-bold text-sm" x-text="selectedNode ? selectedNode.label : ''"></span>
<button class="btn btn-ghost btn-sm" @click="showNodeEditor = false">&times;</button>
<button class="btn btn-ghost btn-sm" @click="showNodeEditor = false"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg></button>
</div>
<template x-if="selectedNode">
<div>
@@ -1651,7 +1645,7 @@
<template x-if="showTomlPreview">
<div class="modal-overlay" @click.self="showTomlPreview = false" @keydown.escape.window="showTomlPreview = false">
<div class="modal" style="max-width:600px">
<div class="modal-header"><h3>Generated TOML</h3><button class="modal-close" @click="showTomlPreview = false">&times;</button></div>
<div class="modal-header"><h3>Generated TOML</h3><button class="modal-close" @click="showTomlPreview = false"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg></button></div>
<pre class="wf-toml-preview" x-text="tomlOutput"></pre>
<button class="btn btn-ghost btn-block mt-2" @click="navigator.clipboard.writeText(tomlOutput); OpenFangToast.success('Copied!')">Copy to Clipboard</button>
</div>
@@ -1662,7 +1656,7 @@
<template x-if="showSaveModal">
<div class="modal-overlay" @click.self="showSaveModal = false" @keydown.escape.window="showSaveModal = false">
<div class="modal">
<div class="modal-header"><h3>Save Workflow</h3><button class="modal-close" @click="showSaveModal = false">&times;</button></div>
<div class="modal-header"><h3>Save Workflow</h3><button class="modal-close" @click="showSaveModal = false"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg></button></div>
<div class="form-group"><label>Name</label><input class="form-input" x-model="workflowName" placeholder="my-workflow"></div>
<div class="form-group"><label>Description</label><input class="form-input" x-model="workflowDescription" placeholder="What does this workflow do?"></div>
<div class="text-xs text-dim mb-4" x-text="nodes.filter(function(n){return n.type!=='start'&&n.type!=='end'}).length + ' steps, ' + connections.length + ' connections'"></div>
@@ -1778,7 +1772,7 @@
<div class="modal">
<div class="modal-header">
<h3>Create Scheduled Job</h3>
<button class="modal-close" @click="showCreateForm = false">&times;</button>
<button class="modal-close" @click="showCreateForm = false"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg></button>
</div>
<div class="form-group">
@@ -1988,28 +1982,28 @@
</h3>
<div class="text-xs text-dim mt-1" x-text="setupModal.quick_setup || setupModal.description"></div>
</div>
<button class="modal-close" @click="setupModal = null">&times;</button>
<button class="modal-close" @click="setupModal = null"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg></button>
</div>
<!-- 3-step progress indicator (non-QR channels only) -->
<div class="channel-steps" x-show="!isQrChannel()">
<div class="channel-step-item">
<div class="channel-step-num" :class="{ active: setupStep === 1, done: setupStep > 1 }">
<span x-show="setupStep <= 1">1</span><span x-show="setupStep > 1">&#10003;</span>
<span x-show="setupStep <= 1">1</span><span x-show="setupStep > 1"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" style="display:block"><polyline points="20 6 9 17 4 12"/></svg></span>
</div>
<span class="channel-step-label" :class="{ active: setupStep === 1, done: setupStep > 1 }">Configure</span>
</div>
<div class="channel-step-line" :class="{ done: setupStep > 1 }"></div>
<div class="channel-step-item">
<div class="channel-step-num" :class="{ active: setupStep === 2, done: setupStep > 2 }">
<span x-show="setupStep <= 2">2</span><span x-show="setupStep > 2">&#10003;</span>
<span x-show="setupStep <= 2">2</span><span x-show="setupStep > 2"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" style="display:block"><polyline points="20 6 9 17 4 12"/></svg></span>
</div>
<span class="channel-step-label" :class="{ active: setupStep === 2, done: setupStep > 2 }">Verify</span>
</div>
<div class="channel-step-line" :class="{ done: setupStep > 2 }"></div>
<div class="channel-step-item">
<div class="channel-step-num" :class="{ active: setupStep === 3, done: setupStep >= 3 }">
<span x-show="setupStep < 3">3</span><span x-show="setupStep >= 3">&#10003;</span>
<span x-show="setupStep < 3">3</span><span x-show="setupStep >= 3"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" style="display:block"><polyline points="20 6 9 17 4 12"/></svg></span>
</div>
<span class="channel-step-label" :class="{ active: setupStep === 3, done: setupStep >= 3 }">Ready</span>
</div>
@@ -2017,7 +2011,9 @@
<!-- Ready panel (step 3) for non-QR channels -->
<div x-show="!isQrChannel() && setupStep === 3 && testPassed" class="ready-panel">
<div class="ready-panel-icon">&#10003;</div>
<div class="ready-panel-icon">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</div>
<div class="ready-panel-title" x-text="setupModal.display_name + ' is ready!'"></div>
<div class="ready-panel-desc">
Your channel is configured and verified. It will activate automatically.
@@ -2053,7 +2049,9 @@
<!-- QR: Connected! -->
<div x-show="!qr.loading && qr.connected" style="text-align:center;padding:2rem 0">
<div style="font-size:3rem;margin-bottom:0.5rem">&#10003;</div>
<div style="margin-bottom:0.5rem;display:flex;justify-content:center">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</div>
<p class="text-sm" style="font-weight:600" x-text="qr.message || 'WhatsApp linked successfully!'"></p>
<p class="text-xs text-dim mt-1">Channel will activate automatically.</p>
</div>
@@ -2261,7 +2259,9 @@
<div class="search-input mb-4" style="position:relative">
<span style="color:var(--text-muted)"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg></span>
<input placeholder="Search ClawHub skills... (type to search)" x-model="clawhubSearch" @input="onSearchInput()" @keydown.enter="if(!$event.isComposing && $event.keyCode !== 229) searchClawHub()" @keydown.escape="clearSearch()" x-ref="clawhubSearchInput">
<button x-show="clawhubSearch" @click="clearSearch()" class="search-clear-btn" title="Clear search (Esc)">&times;</button>
<button x-show="clawhubSearch" @click="clearSearch()" class="search-clear-btn" title="Clear search (Esc)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
</div>
<!-- Sort pills (always visible when not searching) -->
@@ -2458,7 +2458,9 @@ args = ["-y", "@modelcontextprotocol/server-filesystem", "/path"]</pre>
<div>
<div class="modal-header">
<h3 x-text="skillDetail.name || skillDetail.slug"></h3>
<button class="modal-close" @click="closeDetail()">&times;</button>
<button class="modal-close" @click="closeDetail()">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
</div>
<div class="mb-3">
<div class="flex gap-2 items-center flex-wrap">
@@ -2651,7 +2653,9 @@ args = ["-y", "@modelcontextprotocol/server-filesystem", "/path"]</pre>
<div class="modal" style="max-width:600px">
<div class="modal-header">
<h3><span x-text="detailHand.icon"></span> <span x-text="detailHand.name"></span></h3>
<button class="modal-close" @click="detailHand = null">&times;</button>
<button class="modal-close" @click="detailHand = null">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
</div>
<div class="mb-3">
<p x-text="detailHand.description"></p>
@@ -2705,7 +2709,9 @@ args = ["-y", "@modelcontextprotocol/server-filesystem", "/path"]</pre>
<div class="wizard-title" x-text="'Set up ' + setupWizard.name"></div>
<div class="wizard-subtitle" x-text="setupWizard.description"></div>
</div>
<button class="wizard-close" @click="closeSetupWizard()">&times;</button>
<button class="wizard-close" @click="closeSetupWizard()">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
</div>
<!-- Step Indicators -->
@@ -2747,7 +2753,10 @@ args = ["-y", "@modelcontextprotocol/server-filesystem", "/path"]</pre>
<!-- Satisfied: green message -->
<template x-if="req.satisfied">
<div class="dep-met-msg">&check; Detected on your system</div>
<div class="dep-met-msg" style="display:flex;align-items:center;gap:4px">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
<span>Detected on your system</span>
</div>
</template>
<!-- Not satisfied: show install instructions -->
@@ -2784,7 +2793,10 @@ args = ["-y", "@modelcontextprotocol/server-filesystem", "/path"]</pre>
<div style="margin-bottom:10px">
<label class="text-xs text-dim" style="display:block;margin-bottom:4px" x-text="'Paste your ' + req.label + ':'"></label>
<input type="password" class="form-input" x-model="apiKeyInputs[req.key]" :placeholder="req.label" style="width:100%;font-family:var(--font-mono);font-size:12px">
<div class="text-xs" style="margin-top:4px;color:var(--green)" x-show="apiKeyInputs[req.key] && apiKeyInputs[req.key].trim() !== ''">&check; Token entered</div>
<div class="text-xs" style="margin-top:4px;color:var(--green);display:flex;align-items:center;gap:4px" x-show="apiKeyInputs[req.key] && apiKeyInputs[req.key].trim() !== ''">
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
<span>Token entered</span>
</div>
</div>
<details style="margin-bottom:8px">
<summary class="text-xs text-dim" style="cursor:pointer;user-select:none">Or set as environment variable</summary>
@@ -2838,13 +2850,17 @@ args = ["-y", "@modelcontextprotocol/server-filesystem", "/path"]</pre>
</template>
<template x-if="installProgress.status === 'done' && !installProgress.error">
<div class="flex items-center gap-2">
<span style="color:var(--green);font-size:16px">&check;</span>
<span style="color:var(--green);display:flex">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
<span class="text-sm font-bold" style="color:var(--green)">Installation complete!</span>
</div>
</template>
<template x-if="installProgress.error">
<div class="flex items-center gap-2">
<span style="color:var(--red);font-size:16px">&cross;</span>
<span style="color:var(--red);display:flex">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</span>
<span class="text-sm" style="color:var(--red)" x-text="installProgress.error"></span>
</div>
</template>
@@ -3059,7 +3075,9 @@ args = ["-y", "@modelcontextprotocol/server-filesystem", "/path"]</pre>
</div>
<div class="flex items-center gap-2">
<button class="btn btn-ghost btn-sm" @click="refreshDashboard()">Refresh</button>
<button class="modal-close" @click="closeDashboard()">&times;</button>
<button class="modal-close" @click="closeDashboard()">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
</div>
</div>
@@ -3610,12 +3628,12 @@ args = ["-y", "@modelcontextprotocol/server-filesystem", "/path"]</pre>
<div class="card mt-4" style="border-left:3px solid var(--accent)">
<div class="font-bold" style="font-size:13px;margin-bottom:6px">Security Dependencies</div>
<div class="text-xs text-dim" style="line-height:1.8">
<code style="color:var(--accent-light)">sha2</code> SHA-256 &middot;
<code style="color:var(--accent-light)">hmac</code> HMAC-SHA256 &middot;
<code style="color:var(--accent-light)">subtle</code> constant-time &middot;
<code style="color:var(--accent-light)">ed25519-dalek</code> signing &middot;
<code style="color:var(--accent-light)">zeroize</code> secret wiping &middot;
<code style="color:var(--accent-light)">rand</code> randomness &middot;
<code style="color:var(--accent-light)">sha2</code> SHA-256&nbsp;<svg width="3" height="3" viewBox="0 0 24 24" fill="currentColor" style="display:inline-block;vertical-align:middle;opacity:0.4"><circle cx="12" cy="12" r="10"/></svg>
<code style="color:var(--accent-light)">hmac</code> HMAC-SHA256&nbsp;<svg width="3" height="3" viewBox="0 0 24 24" fill="currentColor" style="display:inline-block;vertical-align:middle;opacity:0.4"><circle cx="12" cy="12" r="10"/></svg>
<code style="color:var(--accent-light)">subtle</code> constant-time&nbsp;<svg width="3" height="3" viewBox="0 0 24 24" fill="currentColor" style="display:inline-block;vertical-align:middle;opacity:0.4"><circle cx="12" cy="12" r="10"/></svg>
<code style="color:var(--accent-light)">ed25519-dalek</code> signing&nbsp;<svg width="3" height="3" viewBox="0 0 24 24" fill="currentColor" style="display:inline-block;vertical-align:middle;opacity:0.4"><circle cx="12" cy="12" r="10"/></svg>
<code style="color:var(--accent-light)">zeroize</code> secret wiping&nbsp;<svg width="3" height="3" viewBox="0 0 24 24" fill="currentColor" style="display:inline-block;vertical-align:middle;opacity:0.4"><circle cx="12" cy="12" r="10"/></svg>
<code style="color:var(--accent-light)">rand</code> randomness&nbsp;<svg width="3" height="3" viewBox="0 0 24 24" fill="currentColor" style="display:inline-block;vertical-align:middle;opacity:0.4"><circle cx="12" cy="12" r="10"/></svg>
<code style="color:var(--accent-light)">governor</code> rate limiting
</div>
</div>
@@ -3642,7 +3660,7 @@ args = ["-y", "@modelcontextprotocol/server-filesystem", "/path"]</pre>
<span class="badge" :class="netStatus && netStatus.enabled ? 'badge-success' : 'badge-muted'" x-text="netStatus && netStatus.enabled ? 'Enabled' : 'Disabled'" style="font-size:12px;padding:4px 10px"></span>
</div>
<div x-show="netStatus && netStatus.enabled" class="text-xs text-dim mb-2" style="margin-top:-8px">
Node: <span x-text="netStatus?.node_id?.substring(0,8) + '...'" class="font-bold"></span> &bull;
Node: <span x-text="netStatus?.node_id?.substring(0,8) + '...'" class="font-bold"></span>&nbsp;<svg width="4" height="4" viewBox="0 0 24 24" fill="currentColor" style="display:inline-block;vertical-align:middle;opacity:0.5"><circle cx="12" cy="12" r="10"/></svg>
Listening on <span x-text="netStatus?.listen_address" class="font-bold"></span>
</div>
<div x-show="peersLoading" class="loading-state"><div class="spinner"></div><span>Loading peers...</span></div>
@@ -4259,7 +4277,7 @@ args = ["-y", "@modelcontextprotocol/server-filesystem", "/path"]</pre>
<template x-if="showAdd">
<div class="modal-overlay" @click.self="showAdd = false" @keydown.escape.window="showAdd = false">
<div class="modal">
<div class="modal-header"><h3>Add Key</h3><button class="modal-close" @click="showAdd = false">&times;</button></div>
<div class="modal-header"><h3>Add Key</h3><button class="modal-close" @click="showAdd = false"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg></button></div>
<div class="form-group"><label>Key</label><input class="form-input" x-model="newKey" placeholder="my_key"></div>
<div class="form-group"><label>Value (JSON)</label><textarea class="form-textarea" x-model="newValue" placeholder='"hello"'></textarea></div>
<button class="btn btn-primary btn-block" @click="addKey()">Save</button>
@@ -4586,7 +4604,9 @@ args = ["-y", "@modelcontextprotocol/server-filesystem", "/path"]</pre>
<div class="wizard-progress-step" :class="{ 'wiz-active': step === n, 'wiz-done': step > n }" @click="goToStep(n)">
<div class="wizard-progress-circle">
<span x-show="step <= n" x-text="n"></span>
<span x-show="step > n">&#10003;</span>
<span x-show="step > n">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" style="display:block"><polyline points="20 6 9 17 4 12"/></svg>
</span>
</div>
<span class="wizard-progress-label" x-text="stepLabel(n)"></span>
</div>
@@ -4727,7 +4747,9 @@ args = ["-y", "@modelcontextprotocol/server-filesystem", "/path"]</pre>
<template x-if="selectedProviderObj && providerIsConfigured(selectedProviderObj)">
<div class="card" style="border-left:3px solid var(--success);margin-top:16px">
<div class="flex items-center gap-2">
<span style="color:var(--success);font-size:18px">&#10003;</span>
<div style="color:var(--success);display:flex;align-items:center;justify-content:center;width:24px;height:24px">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</div>
<div>
<div class="font-bold" style="font-size:13px" x-text="selectedProviderObj.display_name + ' is configured and ready'"></div>
<div class="text-xs text-dim">You can test the connection or continue to the next step.</div>
@@ -4896,7 +4918,9 @@ args = ["-y", "@modelcontextprotocol/server-filesystem", "/path"]</pre>
<!-- Step 6: Done -->
<div class="wizard-step" x-show="step === 6">
<div class="wizard-card" style="text-align:center;max-width:560px;margin:0 auto">
<div style="font-size:56px;margin-bottom:12px;color:var(--success)">&#10003;</div>
<div style="margin-bottom:12px;color:var(--success);display:flex;justify-content:center">
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</div>
<h3 style="font-size:20px;font-weight:700;margin-bottom:8px;color:var(--accent)">You're All Set!</h3>
<p style="font-size:13px;color:var(--text-dim);line-height:1.8;margin-bottom:24px">OpenFang is configured and ready to go. Here is a summary of what was set up:</p>
+1 -2
View File
@@ -7,7 +7,6 @@
"background_color": "#0a0a0f",
"theme_color": "#6366f1",
"icons": [
{"src": "/logo.png", "sizes": "192x192", "type": "image/png"},
{"src": "/logo.png", "sizes": "512x512", "type": "image/png"}
{"src": "/logo.png", "sizes": "128x128", "type": "image/png"}
]
}