feat(automation): complete unified automation system redesign

Phase 4 completion:
- Add ApprovalQueue component for managing pending approvals
- Add ExecutionResult component for displaying hand/workflow results
- Update Sidebar navigation to use unified AutomationPanel
- Replace separate 'hands' and 'workflow' tabs with single 'automation' tab
- Fix TypeScript type safety issues with unknown types in JSX expressions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-03-18 17:12:05 +08:00
parent 3a7631e035
commit 3518fc8ece
74 changed files with 4984 additions and 687 deletions

View File

@@ -0,0 +1,537 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WorkBuddy - 技能</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
overflow: hidden;
}
/* Custom scrollbar */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #d1d5db; border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: #9ca3af; }
/* Skill card animations */
.skill-card {
transition: all 0.2s ease;
border: 1px solid #e5e7eb;
}
.skill-card:hover {
border-color: #d1d5db;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03);
transform: translateY(-1px);
}
/* Add button animation */
.add-btn {
transition: all 0.2s ease;
border: 1px solid #e5e7eb;
}
.add-btn:hover {
border-color: #10b981;
color: #10b981;
background-color: #ecfdf5;
transform: scale(1.05);
}
.add-btn:active {
transform: scale(0.95);
}
.add-btn.added {
background-color: #10b981;
border-color: #10b981;
color: white;
}
/* Icon container */
.skill-icon {
background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
transition: all 0.2s ease;
}
.skill-card:hover .skill-icon {
background: linear-gradient(135deg, #e5e7eb 0%, #d1d5db 100%);
}
/* Sidebar active state */
.sidebar-item.active {
background-color: #f3f4f6;
font-weight: 500;
}
/* Search input focus */
.search-input:focus {
border-color: #10b981;
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1);
}
/* Import button hover */
.import-btn {
transition: all 0.2s ease;
}
.import-btn:hover {
background-color: #f3f4f6;
border-color: #d1d5db;
}
</style>
</head>
<body class="bg-white h-screen flex text-gray-800">
<!-- Left Sidebar -->
<aside class="w-64 bg-white border-r border-gray-200 flex flex-col h-full flex-shrink-0">
<!-- App Header -->
<div class="h-14 flex items-center px-4 border-b border-gray-100 flex-shrink-0">
<div class="flex items-center gap-2">
<div class="w-8 h-8 bg-gradient-to-br from-emerald-400 to-teal-500 rounded-lg flex items-center justify-center text-white font-bold text-lg shadow-sm">
<i class="fas fa-code text-sm"></i>
</div>
<span class="font-bold text-lg text-gray-800 tracking-tight">WorkBuddy</span>
</div>
<button class="ml-auto p-1.5 hover:bg-gray-100 rounded-lg text-gray-400 transition-colors">
<i class="far fa-clone text-sm"></i>
</button>
</div>
<!-- Menu Items -->
<nav class="flex-1 py-3 px-3 space-y-0.5 overflow-y-auto">
<!-- Search -->
<div class="px-1 mb-3">
<div class="relative">
<i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 text-sm"></i>
<input type="text" placeholder="搜索任务"
class="w-full pl-9 pr-8 py-2 bg-gray-50 border border-gray-200 rounded-lg text-sm focus:outline-none focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500 transition-all">
<button class="absolute right-2 top-1/2 -translate-y-1/2 p-1 hover:bg-gray-200 rounded text-gray-400 transition-colors">
<i class="fas fa-sliders-h text-xs"></i>
</button>
</div>
</div>
<!-- New Task Button -->
<button class="w-full flex items-center gap-3 px-3 py-2 hover:bg-gray-100 rounded-lg text-gray-700 transition-colors mb-1">
<i class="far fa-check-square text-emerald-600 w-5"></i>
<span class="font-medium">新建任务</span>
</button>
<!-- Nav Items -->
<a href="#" class="sidebar-item flex items-center gap-3 px-3 py-2 text-gray-600 rounded-lg transition-colors">
<i class="fas fa-robot text-gray-400 w-5"></i>
<span>Claw</span>
</a>
<a href="#" class="sidebar-item flex items-center gap-3 px-3 py-2 text-gray-600 rounded-lg transition-colors">
<i class="fas fa-user-tie text-gray-400 w-5"></i>
<span>专家</span>
</a>
<a href="#" class="sidebar-item active flex items-center gap-3 px-3 py-2 text-gray-900 rounded-lg transition-colors">
<i class="fas fa-wand-magic-sparkles text-gray-700 w-5"></i>
<span>技能</span>
</a>
<a href="#" class="sidebar-item flex items-center gap-3 px-3 py-2 text-gray-600 rounded-lg transition-colors">
<i class="fas fa-puzzle-piece text-gray-400 w-5"></i>
<span>插件</span>
</a>
<a href="#" class="sidebar-item flex items-center gap-3 px-3 py-2 text-gray-600 rounded-lg transition-colors">
<i class="far fa-clock text-gray-400 w-5"></i>
<span>自动化</span>
</a>
<!-- Empty State -->
<div class="mt-8 px-4 text-center">
<p class="text-gray-900 font-medium mb-1">暂无任务</p>
<p class="text-gray-400 text-sm">点击上方按钮开始新任务</p>
</div>
</nav>
<!-- User Profile -->
<div class="p-3 border-t border-gray-200 flex-shrink-0">
<button class="flex items-center gap-3 w-full hover:bg-gray-50 p-2 rounded-lg transition-colors">
<div class="w-8 h-8 bg-gradient-to-br from-emerald-400 to-cyan-500 rounded-full flex items-center justify-center text-white font-bold shadow-sm">
<i class="fas fa-robot text-xs"></i>
</div>
<span class="flex-1 text-left text-sm font-medium text-gray-700">iven</span>
<i class="fas fa-chevron-right text-gray-400 text-xs"></i>
</button>
</div>
</aside>
<!-- Main Content -->
<main class="flex-1 flex flex-col h-full bg-white overflow-hidden">
<!-- Top Bar -->
<header class="h-14 bg-white border-b border-gray-200 flex items-center justify-between px-6 flex-shrink-0">
<div class="flex items-center gap-2">
<div class="w-8 h-8 bg-gradient-to-br from-emerald-400 to-teal-500 rounded-lg flex items-center justify-center text-white font-bold">
<i class="fas fa-code text-sm"></i>
</div>
<span class="font-bold text-lg">WorkBuddy</span>
</div>
<div class="text-sm text-gray-600 font-medium">Agents</div>
<div class="flex items-center gap-2">
<button class="p-2 hover:bg-gray-100 rounded-lg text-gray-500">
<i class="fas fa-minus text-xs"></i>
</button>
<button class="p-2 hover:bg-gray-100 rounded-lg text-gray-500">
<i class="far fa-square text-xs"></i>
</button>
<button class="p-2 hover:bg-red-50 rounded-lg text-gray-500 hover:text-red-500">
<i class="fas fa-times text-xs"></i>
</button>
</div>
</header>
<!-- Content Area -->
<div class="flex-1 overflow-y-auto bg-white">
<div class="max-w-6xl mx-auto px-8 py-8">
<!-- Header Section -->
<div class="flex items-start justify-between mb-8">
<div>
<h1 class="text-2xl font-bold text-gray-900 mb-1">技能</h1>
<p class="text-gray-500 text-base">赋予 WorkBuddy 更强大的能力</p>
</div>
<div class="flex items-center gap-3">
<!-- Search -->
<div class="relative">
<i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 text-sm"></i>
<input type="text" id="skillSearch" placeholder="搜索技能"
class="search-input w-64 pl-9 pr-4 py-2 bg-white border border-gray-300 rounded-lg text-sm outline-none transition-all">
</div>
<!-- Import Button -->
<button class="import-btn flex items-center gap-2 px-4 py-2 border border-gray-300 rounded-lg text-sm font-medium text-gray-700 bg-white hover:bg-gray-50" onclick="importSkill()">
<i class="fas fa-plus text-xs"></i>
导入技能
</button>
</div>
</div>
<!-- Installed Count -->
<div class="mb-6">
<span class="text-gray-900 font-medium">已安装</span>
<span class="ml-2 px-2 py-0.5 bg-gray-100 text-gray-600 text-xs rounded-full" id="installedCount">0</span>
</div>
<!-- Recommended Section -->
<div>
<h2 class="text-base font-semibold text-gray-900 mb-4">推荐</h2>
<!-- Skills Grid -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4" id="skillsGrid">
<!-- Skill Card 1 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="find-skills">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">find-skills</h3>
<p class="text-gray-500 text-sm truncate">Helps users discover and install agent skills whe...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'find-skills')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 2 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="workbuddy-channel-setup">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">workbuddy-channel-setup</h3>
<p class="text-gray-500 text-sm truncate">Automate WorkBuddy channel integration setu...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'workbuddy-channel-setup')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 3 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="FBS-BookWriter">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">FBS-BookWriter</h3>
<p class="text-gray-500 text-sm truncate">福帮手出品 | 人机协同写书。联网校验-千书千面...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'FBS-BookWriter')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 4 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="xiaohongshu">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">xiaohongshu</h3>
<p class="text-gray-500 text-sm truncate">小红书RedNote内容工具。使用场景搜索...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'xiaohongshu')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 5 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="agentmail">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">agentmail</h3>
<p class="text-gray-500 text-sm truncate">Email inbox for AI agents. Check messages, sen...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'agentmail')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 6 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="apple-notes">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">apple-notes</h3>
<p class="text-gray-500 text-sm truncate">Manage Apple Notes via the `memo` CLI on ma...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'apple-notes')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 7 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="apple-reminders">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">apple-reminders</h3>
<p class="text-gray-500 text-sm truncate">Manage Apple Reminders via the `remindctl` CL...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'apple-reminders')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 8 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="blogwatcher">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">blogwatcher</h3>
<p class="text-gray-500 text-sm truncate">Monitor blogs and RSS/Atom feeds for updates...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'blogwatcher')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 9 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="cos-vectors">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">cos-vectors</h3>
<p class="text-gray-500 text-sm truncate">Manage Tencent Cloud COS vector buckets via ...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'cos-vectors')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 10 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="gifgrep">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">gifgrep</h3>
<p class="text-gray-500 text-sm truncate">Search GIF providers with CLI/TUI, download re...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'gifgrep')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 11 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="github">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">github</h3>
<p class="text-gray-500 text-sm truncate">Interact with GitHub using the `gh` CLI. Use `gh ...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'github')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 12 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="gog">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">gog</h3>
<p class="text-gray-500 text-sm truncate">Google Workspace CLI for Gmail, Calendar, Driv...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'gog')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 13 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="healthcheck">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">healthcheck</h3>
<p class="text-gray-500 text-sm truncate">Track water and sleep with JSON file storage.</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'healthcheck')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
<!-- Skill Card 14 -->
<div class="skill-card bg-white rounded-xl p-4 flex items-center gap-4 group cursor-pointer" data-name="himalaya">
<div class="skill-icon w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0">
<i class="fas fa-hammer text-gray-600 text-lg"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 text-sm mb-0.5">himalaya</h3>
<p class="text-gray-500 text-sm truncate">CLI to manage emails via IMAP/SMTP. Use `him...</p>
</div>
<button class="add-btn w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-emerald-600 flex-shrink-0" onclick="toggleSkill(this, 'himalaya')">
<i class="fas fa-plus text-sm"></i>
</button>
</div>
</div>
</div>
<!-- Empty State (Hidden by default) -->
<div id="emptyState" class="hidden mt-12 text-center py-12">
<div class="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
<i class="fas fa-search text-gray-400 text-2xl"></i>
</div>
<p class="text-gray-500">未找到相关技能</p>
</div>
</div>
</div>
</main>
<!-- Toast Notification -->
<div id="toast" class="fixed bottom-8 right-8 bg-gray-900 text-white px-6 py-3 rounded-xl shadow-2xl transform translate-y-20 opacity-0 transition-all duration-300 flex items-center gap-3 z-50">
<i class="fas fa-check-circle text-emerald-400"></i>
<span id="toastMessage">操作成功</span>
</div>
<script>
let installedCount = 0;
const installedSkills = new Set();
// Toggle skill installation
function toggleSkill(btn, skillName) {
const icon = btn.querySelector('i');
if (installedSkills.has(skillName)) {
// Uninstall
installedSkills.delete(skillName);
btn.classList.remove('added');
icon.classList.remove('fa-check');
icon.classList.add('fa-plus');
installedCount--;
showToast(`已卸载 ${skillName}`);
} else {
// Install
installedSkills.add(skillName);
btn.classList.add('added');
icon.classList.remove('fa-plus');
icon.classList.add('fa-check');
installedCount++;
showToast(`已安装 ${skillName}`);
}
document.getElementById('installedCount').textContent = installedCount;
}
// Import skill action
function importSkill() {
showToast('请选择要导入的技能文件');
}
// Show toast notification
function showToast(message) {
const toast = document.getElementById('toast');
const toastMessage = document.getElementById('toastMessage');
toastMessage.textContent = message;
toast.classList.remove('translate-y-20', 'opacity-0');
setTimeout(() => {
toast.classList.add('translate-y-20', 'opacity-0');
}, 2500);
}
// Search functionality
document.getElementById('skillSearch').addEventListener('input', function(e) {
const searchTerm = e.target.value.toLowerCase();
const cards = document.querySelectorAll('.skill-card');
let visibleCount = 0;
cards.forEach(card => {
const name = card.getAttribute('data-name').toLowerCase();
const desc = card.querySelector('p').textContent.toLowerCase();
if (name.includes(searchTerm) || desc.includes(searchTerm)) {
card.style.display = 'flex';
visibleCount++;
} else {
card.style.display = 'none';
}
});
// Show/hide empty state
const emptyState = document.getElementById('emptyState');
if (visibleCount === 0) {
emptyState.classList.remove('hidden');
} else {
emptyState.classList.add('hidden');
}
});
// Add stagger animation on load
window.addEventListener('load', () => {
const cards = document.querySelectorAll('.skill-card');
cards.forEach((card, index) => {
card.style.opacity = '0';
card.style.transform = 'translateY(10px)';
setTimeout(() => {
card.style.transition = 'all 0.3s ease';
card.style.opacity = '1';
card.style.transform = 'translateY(0)';
}, index * 30);
});
});
</script>
</body>
</html>