首页布局优化前

This commit is contained in:
iven
2026-03-17 23:26:16 +08:00
parent 74dbf42644
commit e262200f1e
89 changed files with 2266 additions and 2120 deletions

View File

@@ -4,7 +4,7 @@
* 展示学习事件、模式和系统建议。
*/
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import {
Brain,
@@ -12,24 +12,17 @@ import {
Lightbulb,
Check,
X,
RefreshCw,
Download,
Upload,
Settings,
BarChart3,
Clock,
Zap,
} from 'lucide-react';
import { Button, Badge, EmptyState } from './ui';
import { Button, EmptyState } from './ui';
import { useActiveLearningStore } from '../store/activeLearningStore';
import {
useActiveLearningStore,
type LearningEvent,
type LearningPattern,
type LearningSuggestion,
type LearningEventType,
} from '../store/activeLearningStore';
} from '../types/active-learning';
import { useChatStore } from '../store/chatStore';
import { cardHover, defaultTransition } from '../lib/animations';
// === Constants ===
@@ -74,9 +67,9 @@ function EventItem({ event, onAcknowledge }: EventItemProps) {
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<Badge variant="ghost" className={typeInfo.color}>
<span className={`text-xs px-2 py-0.5 rounded ${typeInfo.color}`}>
{typeInfo.label}
</Badge>
</span>
<span className="text-xs text-gray-500">{timeAgo}</span>
</div>
<p className="text-sm text-gray-300 truncate">{event.observation}</p>
@@ -133,14 +126,13 @@ function SuggestionCard({ suggestion, onApply, onDismiss }: SuggestionCardProps)
<div className="flex items-center gap-2 mt-3">
<Button variant="primary" size="sm" onClick={onApply}>
<Check className="w-3 h-3 mr-1" />
</Button>
<Check className="w-3 h-3 mr-1" />
</Button>
<Button variant="ghost" size="sm" onClick={onDismiss}>
<X className="w-3 h-3 mr-1" />
</Button>
</div>
<X className="w-3 h-3 mr-1" />
</Button>
</div>
</motion.div>
);
@@ -160,10 +152,7 @@ export function ActiveLearningPanel({ className = '' }: ActiveLearningPanelProps
const {
events,
patterns,
suggestions,
config,
isLoading,
acknowledgeEvent,
getPatterns,
getSuggestions,
@@ -364,9 +353,9 @@ export function ActiveLearningPanel({ className = '' }: ActiveLearningPanelProps
<span>{typeInfo.icon}</span>
<span className="text-sm font-medium text-white">{typeInfo.label}</span>
</div>
<Badge variant="ghost">
<span className="text-xs px-2 py-0.5 rounded bg-gray-700 text-gray-300">
{(pattern.confidence * 100).toFixed(0)}%
</Badge>
</span>
</div>
<p className="text-sm text-gray-400">{pattern.description}</p>
<div className="mt-2 text-xs text-gray-500">

View File

@@ -77,7 +77,7 @@ const steps = [
// === Component ===
export function AgentOnboardingWizard({ isOpen, onClose, onSuccess }: AgentOnboardingWizardProps) {
const { createClone, isLoading, error, clearError } = useAgentStore();
const { createClone, updateClone, clones, isLoading, error, clearError } = useAgentStore();
const [currentStep, setCurrentStep] = useState(1);
const [formData, setFormData] = useState<WizardFormData>(initialFormData);
const [errors, setErrors] = useState<Record<string, string>>({});
@@ -164,7 +164,7 @@ export function AgentOnboardingWizard({ isOpen, onClose, onSuccess }: AgentOnboa
setSubmitStatus('idle');
try {
const createOptions: CloneCreateOptions = {
const personalityUpdates = {
name: formData.agentName,
role: formData.agentRole || undefined,
nickname: formData.agentNickname || undefined,
@@ -173,13 +173,23 @@ export function AgentOnboardingWizard({ isOpen, onClose, onSuccess }: AgentOnboa
scenarios: formData.scenarios,
workspaceDir: formData.workspaceDir || undefined,
restrictFiles: formData.restrictFiles,
privacyOptIn: formData.privacyOptIn,
emoji: formData.emoji,
personality: formData.personality,
notes: formData.notes || undefined,
};
const clone = await createClone(createOptions);
let clone: Clone | undefined;
// If there's an existing clone, update it instead of creating a new one
if (clones && clones.length > 0) {
clone = await updateClone(clones[0].id, personalityUpdates);
} else {
const createOptions: CloneCreateOptions = {
...personalityUpdates,
privacyOptIn: formData.privacyOptIn,
};
clone = await createClone(createOptions);
}
if (clone) {
setSubmitStatus('success');
@@ -516,32 +526,6 @@ export function AgentOnboardingWizard({ isOpen, onClose, onSuccess }: AgentOnboa
/>
</button>
</div>
<div className="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-800/50 rounded-lg">
<div>
<p className="text-sm font-medium text-gray-900 dark:text-white">
使
</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
使
</p>
</div>
<button
type="button"
onClick={() => updateField('privacyOptIn', !formData.privacyOptIn)}
className={cn(
'w-11 h-6 rounded-full transition-colors relative',
formData.privacyOptIn ? 'bg-primary' : 'bg-gray-300 dark:bg-gray-600'
)}
>
<span
className={cn(
'absolute top-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform',
)}
style={{ left: formData.privacyOptIn ? '22px' : '2px' }}
/>
</button>
</div>
</div>
<div className="mt-4">
@@ -623,7 +607,7 @@ export function AgentOnboardingWizard({ isOpen, onClose, onSuccess }: AgentOnboa
<button
type="button"
onClick={nextStep}
className="px-4 py-2 text-sm text-white bg-primary rounded-lg hover:bg-primary/90 transition-colors flex items-center gap-1"
className="px-4 py-2 text-sm bg-primary/10 text-primary rounded-lg hover:bg-primary/20 transition-colors flex items-center gap-1"
>
<ChevronRight className="w-4 h-4" />
@@ -633,7 +617,7 @@ export function AgentOnboardingWizard({ isOpen, onClose, onSuccess }: AgentOnboa
type="button"
onClick={handleSubmit}
disabled={isLoading || submitStatus === 'success'}
className="px-4 py-2 text-sm text-white bg-primary rounded-lg hover:bg-primary/90 transition-colors disabled:opacity-50 flex items-center gap-2"
className="px-4 py-2 text-sm bg-primary/10 text-primary rounded-lg hover:bg-primary/20 transition-colors disabled:opacity-50 flex items-center gap-2"
>
{isLoading ? (
<>

View File

@@ -9,13 +9,34 @@
import { useState, useEffect, useCallback } from 'react';
import { useGatewayStore, type Hand, type HandRequirement } from '../store/gatewayStore';
import { Zap, RefreshCw, ChevronRight, CheckCircle, XCircle, Loader2, AlertTriangle, Settings } from 'lucide-react';
import { Zap, RefreshCw, ChevronRight, CheckCircle, XCircle, Loader2, AlertTriangle, Settings, Play } from 'lucide-react';
import { BrowserHandCard } from './BrowserHand';
import type { HandParameter } from '../types/hands';
import { HAND_DEFINITIONS } from '../types/hands';
import { HandParamsForm } from './HandParamsForm';
// === Status Badge Component ===
type HandStatus = 'idle' | 'running' | 'needs_approval' | 'error' | 'unavailable' | 'setup_needed';
// === Parameter Validation Helper ===
function validateAllParameters(
parameters: HandParameter[],
values: Record<string, unknown>
): Record<string, string> {
const errors: Record<string, string> = {};
parameters.forEach(param => {
if (param.required) {
const value = values[param.name];
if (value === undefined || value === null || value === '') {
errors[param.name] = `${param.label} is required`;
}
}
});
return errors;
}
interface StatusConfig {
label: string;
className: string;
@@ -117,6 +138,57 @@ interface HandDetailsModalProps {
}
function HandDetailsModal({ hand, isOpen, onClose, onActivate, isActivating }: HandDetailsModalProps) {
// Get Hand parameters from definitions
const handDefinition = HAND_DEFINITIONS.find(h => h.id === hand.id);
const parameters: HandParameter[] = handDefinition?.parameters || [];
// Form state for parameters
const [paramValues, setParamValues] = useState<Record<string, unknown>>({});
const [paramErrors, setParamErrors] = useState<Record<string, string>>({});
const [showParamsForm, setShowParamsForm] = useState(false);
// Initialize default values
useEffect(() => {
if (parameters.length > 0) {
const defaults: Record<string, unknown> = {};
parameters.forEach(p => {
if (p.defaultValue !== undefined) {
defaults[p.name] = p.defaultValue;
}
});
setParamValues(defaults);
}
}, [parameters]);
// Reset form when modal opens/closes
useEffect(() => {
if (isOpen) {
setShowParamsForm(false);
setParamErrors({});
}
}, [isOpen]);
const handleActivateClick = useCallback(() => {
if (parameters.length > 0 && !showParamsForm) {
// Show params form first
setShowParamsForm(true);
return;
}
// Validate parameters if showing form
if (showParamsForm) {
const errors = validateAllParameters(parameters, paramValues);
setParamErrors(errors);
if (Object.keys(errors).length > 0) {
return;
}
// Pass parameters to onActivate
onActivate();
} else {
onActivate();
}
}, [parameters, showParamsForm, paramValues, onActivate]);
if (!isOpen) return null;
const canActivate = hand.status === 'idle' || hand.status === 'setup_needed';
@@ -210,6 +282,23 @@ function HandDetailsModal({ hand, isOpen, onClose, onActivate, isActivating }: H
</div>
)}
{/* Parameters Form (shown when activating) */}
{showParamsForm && parameters.length > 0 && (
<div className="bg-gray-50 dark:bg-gray-900 rounded-lg p-3">
<h3 className="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-3">
</h3>
<HandParamsForm
parameters={parameters}
values={paramValues}
onChange={setParamValues}
errors={paramErrors}
disabled={isActivating}
presetKey={`hand-${hand.id}`}
/>
</div>
)}
{/* Dashboard Metrics */}
{hand.metrics && hand.metrics.length > 0 && (
<div className="bg-gray-50 dark:bg-gray-900 rounded-lg p-3">
@@ -234,13 +323,13 @@ function HandDetailsModal({ hand, isOpen, onClose, onActivate, isActivating }: H
{/* Footer */}
<div className="flex items-center justify-end gap-2 p-4 border-t border-gray-200 dark:border-gray-700">
<button
onClick={onClose}
onClick={showParamsForm ? () => setShowParamsForm(false) : onClose}
className="px-4 py-2 text-sm border border-gray-200 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300"
>
{showParamsForm ? '返回' : '关闭'}
</button>
<button
onClick={onActivate}
onClick={handleActivateClick}
disabled={!canActivate || hasUnmetRequirements || isActivating}
className="px-4 py-2 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
>
@@ -254,6 +343,11 @@ function HandDetailsModal({ hand, isOpen, onClose, onActivate, isActivating }: H
<Settings className="w-4 h-4" />
</>
) : showParamsForm ? (
<>
<Play className="w-4 h-4" />
</>
) : (
<>
<Zap className="w-4 h-4" />

View File

@@ -5,9 +5,13 @@ import { useGatewayStore, type PluginStatus } from '../store/gatewayStore';
import { toChatAgent, useChatStore } from '../store/chatStore';
import {
Wifi, WifiOff, Bot, BarChart3, Plug, RefreshCw,
MessageSquare, Cpu, FileText, User, Activity, FileCode, Brain
MessageSquare, Cpu, FileText, User, Activity, FileCode, Brain,
Shield, Sparkles, GraduationCap
} from 'lucide-react';
import { MemoryPanel } from './MemoryPanel';
import { ReflectionLog } from './ReflectionLog';
import { AutonomyConfig } from './AutonomyConfig';
import { ActiveLearningPanel } from './ActiveLearningPanel';
import { cardHover, defaultTransition } from '../lib/animations';
import { Button, Badge, EmptyState } from './ui';
import { getPersonalityById } from '../lib/personality-presets';
@@ -19,7 +23,7 @@ export function RightPanel() {
connect, loadClones, loadUsageStats, loadPluginStatus, workspaceInfo, quickConfig, updateClone,
} = useGatewayStore();
const { messages, currentModel, currentAgent, setCurrentAgent } = useChatStore();
const [activeTab, setActiveTab] = useState<'status' | 'files' | 'agent' | 'memory'>('status');
const [activeTab, setActiveTab] = useState<'status' | 'files' | 'agent' | 'memory' | 'reflection' | 'autonomy' | 'learning'>('status');
const [isEditingAgent, setIsEditingAgent] = useState(false);
const [agentDraft, setAgentDraft] = useState<AgentDraft | null>(null);
@@ -152,12 +156,54 @@ export function RightPanel() {
>
<Brain className="w-4 h-4" />
</Button>
<Button
variant={activeTab === 'reflection' ? 'secondary' : 'ghost'}
size="sm"
onClick={() => setActiveTab('reflection')}
className="flex items-center gap-1 text-xs px-2 py-1"
title="Reflection"
aria-label="Reflection"
aria-selected={activeTab === 'reflection'}
role="tab"
>
<Sparkles className="w-4 h-4" />
</Button>
<Button
variant={activeTab === 'autonomy' ? 'secondary' : 'ghost'}
size="sm"
onClick={() => setActiveTab('autonomy')}
className="flex items-center gap-1 text-xs px-2 py-1"
title="Autonomy"
aria-label="Autonomy"
aria-selected={activeTab === 'autonomy'}
role="tab"
>
<Shield className="w-4 h-4" />
</Button>
<Button
variant={activeTab === 'learning' ? 'secondary' : 'ghost'}
size="sm"
onClick={() => setActiveTab('learning')}
className="flex items-center gap-1 text-xs px-2 py-1"
title="Learning"
aria-label="Learning"
aria-selected={activeTab === 'learning'}
role="tab"
>
<GraduationCap className="w-4 h-4" />
</Button>
</div>
</div>
<div className="flex-1 overflow-y-auto custom-scrollbar p-4 space-y-4">
{activeTab === 'memory' ? (
<MemoryPanel />
) : activeTab === 'reflection' ? (
<ReflectionLog />
) : activeTab === 'autonomy' ? (
<AutonomyConfig />
) : activeTab === 'learning' ? (
<ActiveLearningPanel />
) : activeTab === 'agent' ? (
<div className="space-y-4">
<motion.div

View File

@@ -1,13 +1,15 @@
/**
* SchedulerPanel - OpenFang Scheduler UI
*
* Displays scheduled jobs, event triggers, and run history.
* Displays scheduled jobs, event triggers, workflows, and run history.
*
* Design based on OpenFang Dashboard v0.4.0
*/
import { useState, useEffect, useCallback } from 'react';
import { useGatewayStore } from '../store/gatewayStore';
import { useGatewayStore, type Workflow } from '../store/gatewayStore';
import { WorkflowEditor } from './WorkflowEditor';
import { WorkflowHistory } from './WorkflowHistory';
import {
Clock,
Zap,
@@ -19,11 +21,13 @@ import {
X,
AlertCircle,
CheckCircle,
GitBranch,
Play,
} from 'lucide-react';
// === Tab Types ===
type TabType = 'scheduled' | 'triggers' | 'history';
type TabType = 'scheduled' | 'triggers' | 'workflows' | 'history';
// === Schedule Type ===
@@ -632,13 +636,26 @@ function CreateJobModal({ isOpen, onClose, onSuccess }: CreateJobModalProps) {
// === Main SchedulerPanel Component ===
export function SchedulerPanel() {
const { scheduledTasks, loadScheduledTasks, isLoading } = useGatewayStore();
const {
scheduledTasks,
loadScheduledTasks,
workflows,
loadWorkflows,
createWorkflow,
executeWorkflow,
isLoading,
} = useGatewayStore();
const [activeTab, setActiveTab] = useState<TabType>('scheduled');
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [isWorkflowEditorOpen, setIsWorkflowEditorOpen] = useState(false);
const [editingWorkflow, setEditingWorkflow] = useState<Workflow | undefined>(undefined);
const [selectedWorkflow, setSelectedWorkflow] = useState<Workflow | null>(null);
const [isSavingWorkflow, setIsSavingWorkflow] = useState(false);
useEffect(() => {
loadScheduledTasks();
}, [loadScheduledTasks]);
loadWorkflows();
}, [loadScheduledTasks, loadWorkflows]);
const handleCreateJob = useCallback(() => {
setIsCreateModalOpen(true);
@@ -653,6 +670,60 @@ export function SchedulerPanel() {
loadScheduledTasks();
}, [loadScheduledTasks]);
// Workflow handlers
const handleCreateWorkflow = useCallback(() => {
setEditingWorkflow(undefined);
setIsWorkflowEditorOpen(true);
}, []);
const handleEditWorkflow = useCallback((workflow: Workflow) => {
setEditingWorkflow(workflow);
setIsWorkflowEditorOpen(true);
}, []);
const handleViewWorkflowHistory = useCallback((workflow: Workflow) => {
setSelectedWorkflow(workflow);
}, []);
const handleSaveWorkflow = useCallback(async (data: {
name: string;
description?: string;
steps: Array<{
handName: string;
name?: string;
params?: Record<string, unknown>;
condition?: string;
}>;
}) => {
setIsSavingWorkflow(true);
try {
if (editingWorkflow) {
// Update existing workflow
console.log('Update workflow:', editingWorkflow.id, data);
} else {
// Create new workflow
await createWorkflow(data);
}
setIsWorkflowEditorOpen(false);
setEditingWorkflow(undefined);
await loadWorkflows();
} catch (error) {
console.error('Failed to save workflow:', error);
throw error;
} finally {
setIsSavingWorkflow(false);
}
}, [editingWorkflow, createWorkflow, loadWorkflows]);
const handleExecuteWorkflow = useCallback(async (workflowId: string) => {
try {
await executeWorkflow(workflowId);
await loadWorkflows();
} catch (error) {
console.error('Failed to execute workflow:', error);
}
}, [executeWorkflow, loadWorkflows]);
if (isLoading && scheduledTasks.length === 0) {
return (
<div className="p-4 text-center">
@@ -706,6 +777,12 @@ export function SchedulerPanel() {
icon={Zap}
label="事件触发器"
/>
<TabButton
active={activeTab === 'workflows'}
onClick={() => setActiveTab('workflows')}
icon={GitBranch}
label="工作流"
/>
<TabButton
active={activeTab === 'history'}
onClick={() => setActiveTab('history')}
@@ -722,6 +799,15 @@ export function SchedulerPanel() {
</button>
)}
{activeTab === 'workflows' && (
<button
onClick={handleCreateWorkflow}
className="flex items-center gap-1.5 px-3 py-1.5 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
<Plus className="w-4 h-4" />
</button>
)}
</div>
{/* Tab Content */}
@@ -787,6 +873,86 @@ export function SchedulerPanel() {
</div>
)}
{/* Workflows Tab */}
{activeTab === 'workflows' && (
selectedWorkflow ? (
<WorkflowHistory
workflow={selectedWorkflow}
onBack={() => setSelectedWorkflow(null)}
/>
) : (
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
{workflows.length === 0 ? (
<EmptyState
icon={GitBranch}
title="暂无工作流"
description="工作流可以将多个 Hand 组合成自动化流程,实现复杂的任务编排。"
actionLabel="创建工作流"
onAction={handleCreateWorkflow}
/>
) : (
<div className="space-y-2">
{workflows.map((workflow) => (
<div
key={workflow.id}
className="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-900 rounded-lg"
>
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-purple-100 dark:bg-purple-900/30 rounded-lg flex items-center justify-center">
<GitBranch className="w-4 h-4 text-purple-600 dark:text-purple-400" />
</div>
<div>
<div className="font-medium text-gray-900 dark:text-white">
{workflow.name}
</div>
<div className="text-xs text-gray-500 dark:text-gray-400">
{workflow.description || '无描述'}
</div>
</div>
</div>
<div className="flex items-center gap-2">
{workflow.steps && (
<span className="text-xs text-gray-500 dark:text-gray-400">
{workflow.steps}
</span>
)}
<button
onClick={() => handleExecuteWorkflow(workflow.id)}
className="p-1.5 text-green-600 hover:bg-green-50 dark:hover:bg-green-900/20 rounded"
title="执行"
>
<Play className="w-4 h-4" />
</button>
<button
onClick={() => handleEditWorkflow(workflow)}
className="p-1.5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded"
title="编辑"
>
<GitBranch className="w-4 h-4" />
</button>
<button
onClick={() => handleViewWorkflowHistory(workflow)}
className="p-1.5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded"
title="历史"
>
<History className="w-4 h-4" />
</button>
</div>
</div>
))}
<button
onClick={handleCreateWorkflow}
className="w-full flex items-center justify-center gap-2 p-3 border-2 border-dashed border-gray-200 dark:border-gray-700 rounded-lg text-gray-500 dark:text-gray-400 hover:border-blue-500 hover:text-blue-500 dark:hover:border-blue-400 dark:hover:text-blue-400 transition-colors"
>
<Plus className="w-4 h-4" />
</button>
</div>
)}
</div>
)
)}
{activeTab === 'history' && (
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
<EmptyState
@@ -804,6 +970,18 @@ export function SchedulerPanel() {
onClose={() => setIsCreateModalOpen(false)}
onSuccess={handleCreateSuccess}
/>
{/* Workflow Editor Modal */}
<WorkflowEditor
workflow={editingWorkflow}
isOpen={isWorkflowEditorOpen}
onClose={() => {
setIsWorkflowEditorOpen(false);
setEditingWorkflow(undefined);
}}
onSave={handleSaveWorkflow}
isSaving={isSavingWorkflow}
/>
</>
);
}

View File

@@ -1,4 +1,5 @@
import { useState } from 'react';
import { useGatewayStore } from '../../store/gatewayStore';
import {
Settings as SettingsIcon,
BarChart3,
@@ -28,6 +29,7 @@ import { About } from './About';
import { Credits } from './Credits';
import { AuditLogsPanel } from '../AuditLogsPanel';
import { SecurityStatus } from '../SecurityStatus';
import { SecurityLayersPanel } from '../SecurityLayersPanel';
import { TaskList } from '../TaskList';
interface SettingsLayoutProps {
@@ -69,6 +71,7 @@ const menuItems: { id: SettingsPage; label: string; icon: React.ReactNode }[] =
export function SettingsLayout({ onBack }: SettingsLayoutProps) {
const [activePage, setActivePage] = useState<SettingsPage>('general');
const { securityStatus } = useGatewayStore();
const renderPage = () => {
switch (activePage) {
@@ -82,9 +85,22 @@ export function SettingsLayout({ onBack }: SettingsLayoutProps) {
case 'workspace': return <Workspace />;
case 'privacy': return <Privacy />;
case 'security': return (
<div className="max-w-3xl">
<h1 className="text-xl font-bold text-gray-900 mb-6"></h1>
<SecurityStatus />
<div className="space-y-6">
<div>
<h1 className="text-xl font-bold text-gray-900 mb-4"></h1>
<SecurityStatus />
</div>
<div>
<h2 className="text-lg font-semibold text-gray-900 mb-4"></h2>
<SecurityLayersPanel
status={securityStatus || {
layers: [],
enabledCount: 0,
totalCount: 16,
securityLevel: 'low',
}}
/>
</div>
</div>
);
case 'audit': return <AuditLogsPanel />;

View File

@@ -1,16 +1,17 @@
import { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Settings, Users, Bot, GitBranch, MessageSquare, Layers } from 'lucide-react';
import { Settings, Users, Bot, GitBranch, MessageSquare, Layers, Package } from 'lucide-react';
import { CloneManager } from './CloneManager';
import { HandList } from './HandList';
import { WorkflowList } from './WorkflowList';
import { TeamList } from './TeamList';
import { SwarmDashboard } from './SwarmDashboard';
import { SkillMarket } from './SkillMarket';
import { useGatewayStore } from '../store/gatewayStore';
import { Button } from './ui';
import { containerVariants, defaultTransition } from '../lib/animations';
export type MainViewType = 'chat' | 'hands' | 'workflow' | 'team' | 'swarm';
export type MainViewType = 'chat' | 'hands' | 'workflow' | 'team' | 'swarm' | 'skills';
interface SidebarProps {
onOpenSettings?: () => void;
@@ -21,12 +22,13 @@ interface SidebarProps {
onSelectTeam?: (teamId: string) => void;
}
type Tab = 'clones' | 'hands' | 'workflow' | 'team' | 'swarm';
type Tab = 'clones' | 'hands' | 'workflow' | 'team' | 'swarm' | 'skills';
const TABS: { key: Tab; label: string; icon: React.ComponentType<{ className?: string }>; mainView?: MainViewType }[] = [
{ key: 'clones', label: '分身', icon: Bot },
{ key: 'hands', label: 'Hands', icon: MessageSquare, mainView: 'hands' },
{ key: 'workflow', label: '工作流', icon: GitBranch, mainView: 'workflow' },
{ key: 'skills', label: '技能', icon: Package, mainView: 'skills' },
{ key: 'team', label: '团队', icon: Users, mainView: 'team' },
{ key: 'swarm', label: '协作', icon: Layers, mainView: 'swarm' },
];
@@ -107,6 +109,7 @@ export function Sidebar({
/>
)}
{activeTab === 'workflow' && <WorkflowList />}
{activeTab === 'skills' && <SkillMarket />}
{activeTab === 'team' && (
<TeamList
selectedTeamId={selectedTeamId}