refactor(skills): add skill-adapter and refactor SkillMarket
- Add skill-adapter.ts to bridge configStore and UI skill formats - Refactor SkillMarket to use new skill-adapter instead of skill-discovery - Add health check state to connectionStore - Update multiple components with improved typing - Clean up test artifacts and add new test results - Update README and add skill-market-mvp plan Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -28,7 +28,7 @@ import {
|
||||
X,
|
||||
Loader2
|
||||
} from 'lucide-react';
|
||||
import { useGatewayStore, AuditLogEntry } from '../store/gatewayStore';
|
||||
import { useSecurityStore, AuditLogEntry } from '../store/securityStore';
|
||||
|
||||
import { getGatewayClient } from '../lib/gateway-client';
|
||||
|
||||
@@ -511,7 +511,9 @@ function HashChainVisualization({ logs, selectedIndex, onSelect, brokenAtIndex }
|
||||
// === Main Component ===
|
||||
|
||||
export function AuditLogsPanel() {
|
||||
const { auditLogs, loadAuditLogs, isLoading } = useGatewayStore();
|
||||
const auditLogs = useSecurityStore((s) => s.auditLogs);
|
||||
const loadAuditLogs = useSecurityStore((s) => s.loadAuditLogs);
|
||||
const isLoading = useSecurityStore((s) => s.auditLogsLoading);
|
||||
const client = getGatewayClient();
|
||||
|
||||
// State
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useGatewayStore } from '../../store/gatewayStore';
|
||||
import { useWorkflowStore } from '../../store/workflowStore';
|
||||
import { useHandStore } from '../../store/handStore';
|
||||
import { useWorkflowStore, type Workflow } from '../../store/workflowStore';
|
||||
import {
|
||||
type AutomationItem,
|
||||
type CategoryType,
|
||||
@@ -51,15 +51,14 @@ export function AutomationPanel({
|
||||
onSelect,
|
||||
showBatchActions = true,
|
||||
}: AutomationPanelProps) {
|
||||
// Store state - use gatewayStore which has the actual data
|
||||
const hands = useGatewayStore(s => s.hands);
|
||||
const workflows = useGatewayStore(s => s.workflows);
|
||||
const isLoading = useGatewayStore(s => s.isLoading);
|
||||
const loadHands = useGatewayStore(s => s.loadHands);
|
||||
const loadWorkflows = useGatewayStore(s => s.loadWorkflows);
|
||||
const triggerHand = useGatewayStore(s => s.triggerHand);
|
||||
// workflowStore for triggerWorkflow (not in gatewayStore)
|
||||
const triggerWorkflow = useWorkflowStore(s => s.triggerWorkflow);
|
||||
// Store state - use domain stores
|
||||
const hands = useHandStore((s) => s.hands);
|
||||
const workflows = useWorkflowStore((s) => s.workflows);
|
||||
const isLoading = useHandStore((s) => s.isLoading) || useWorkflowStore((s) => s.isLoading);
|
||||
const loadHands = useHandStore((s) => s.loadHands);
|
||||
const loadWorkflows = useWorkflowStore((s) => s.loadWorkflows);
|
||||
const triggerHand = useHandStore((s) => s.triggerHand);
|
||||
const triggerWorkflow = useWorkflowStore((s) => s.triggerWorkflow);
|
||||
|
||||
// UI state
|
||||
const [selectedCategory, setSelectedCategory] = useState<CategoryType>(initialCategory);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useGatewayStore } from '../store/gatewayStore';
|
||||
import { useConnectionStore } from '../store/connectionStore';
|
||||
import { useAgentStore } from '../store/agentStore';
|
||||
import { useConfigStore } from '../store/configStore';
|
||||
import { Radio, RefreshCw, MessageCircle, Settings } from 'lucide-react';
|
||||
|
||||
const CHANNEL_ICONS: Record<string, string> = {
|
||||
@@ -20,7 +22,10 @@ interface ChannelListProps {
|
||||
}
|
||||
|
||||
export function ChannelList({ onOpenSettings }: ChannelListProps) {
|
||||
const { channels, connectionState, loadChannels, loadPluginStatus } = useGatewayStore();
|
||||
const connectionState = useConnectionStore((s) => s.connectionState);
|
||||
const loadPluginStatus = useAgentStore((s) => s.loadPluginStatus);
|
||||
const channels = useConfigStore((s) => s.channels);
|
||||
const loadChannels = useConfigStore((s) => s.loadChannels);
|
||||
|
||||
const connected = connectionState === 'connected';
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { useChatStore, Message } from '../store/chatStore';
|
||||
import { useGatewayStore } from '../store/gatewayStore';
|
||||
import { useConnectionStore } from '../store/connectionStore';
|
||||
import { useAgentStore } from '../store/agentStore';
|
||||
import { useConfigStore } from '../store/configStore';
|
||||
import { Paperclip, ChevronDown, Terminal, SquarePen, ArrowUp, MessageSquare, Download, Copy, Check } from 'lucide-react';
|
||||
import { Button, EmptyState } from './ui';
|
||||
import { listItemVariants, defaultTransition, fadeInVariants } from '../lib/animations';
|
||||
@@ -14,7 +16,9 @@ export function ChatArea() {
|
||||
sendMessage: sendToGateway, setCurrentModel, initStreamListener,
|
||||
newConversation,
|
||||
} = useChatStore();
|
||||
const { connectionState, clones, models } = useGatewayStore();
|
||||
const connectionState = useConnectionStore((s) => s.connectionState);
|
||||
const clones = useAgentStore((s) => s.clones);
|
||||
const models = useConfigStore((s) => s.models);
|
||||
|
||||
const [input, setInput] = useState('');
|
||||
const [showModelPicker, setShowModelPicker] = useState(false);
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useGatewayStore } from '../store/gatewayStore';
|
||||
import { useHandStore } from '../store/handStore';
|
||||
import { useWorkflowStore } from '../store/workflowStore';
|
||||
import {
|
||||
Zap,
|
||||
X,
|
||||
@@ -144,7 +145,12 @@ const eventTypeOptions = [
|
||||
// === Component ===
|
||||
|
||||
export function CreateTriggerModal({ isOpen, onClose, onSuccess }: CreateTriggerModalProps) {
|
||||
const { hands, workflows, createTrigger, loadHands, loadWorkflows } = useGatewayStore();
|
||||
// Store state - use domain stores
|
||||
const hands = useHandStore((s) => s.hands);
|
||||
const workflows = useWorkflowStore((s) => s.workflows);
|
||||
const createTrigger = useHandStore((s) => s.createTrigger);
|
||||
const loadHands = useHandStore((s) => s.loadHands);
|
||||
const loadWorkflows = useWorkflowStore((s) => s.loadWorkflows);
|
||||
const [formData, setFormData] = useState<TriggerFormData>(initialFormData);
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { ReactNode, useEffect, useMemo, useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { getStoredGatewayUrl } from '../lib/gateway-client';
|
||||
import { useGatewayStore, type PluginStatus } from '../store/gatewayStore';
|
||||
import { useConnectionStore } from '../store/connectionStore';
|
||||
import { useAgentStore, type PluginStatus } from '../store/agentStore';
|
||||
import { useConfigStore } from '../store/configStore';
|
||||
import { toChatAgent, useChatStore, type CodeBlock } from '../store/chatStore';
|
||||
import {
|
||||
Wifi, WifiOff, Bot, BarChart3, Plug, RefreshCw,
|
||||
@@ -79,10 +81,25 @@ import { getPersonalityById } from '../lib/personality-presets';
|
||||
import { silentErrorHandler } from '../lib/error-utils';
|
||||
|
||||
export function RightPanel() {
|
||||
const {
|
||||
connectionState, gatewayVersion, error, clones, usageStats, pluginStatus,
|
||||
connect, loadClones, loadUsageStats, loadPluginStatus, workspaceInfo, quickConfig, updateClone,
|
||||
} = useGatewayStore();
|
||||
// Connection store
|
||||
const connectionState = useConnectionStore((s) => s.connectionState);
|
||||
const gatewayVersion = useConnectionStore((s) => s.gatewayVersion);
|
||||
const error = useConnectionStore((s) => s.error);
|
||||
const connect = useConnectionStore((s) => s.connect);
|
||||
|
||||
// Agent store
|
||||
const clones = useAgentStore((s) => s.clones);
|
||||
const usageStats = useAgentStore((s) => s.usageStats);
|
||||
const pluginStatus = useAgentStore((s) => s.pluginStatus);
|
||||
const loadClones = useAgentStore((s) => s.loadClones);
|
||||
const loadUsageStats = useAgentStore((s) => s.loadUsageStats);
|
||||
const loadPluginStatus = useAgentStore((s) => s.loadPluginStatus);
|
||||
const updateClone = useAgentStore((s) => s.updateClone);
|
||||
|
||||
// Config store
|
||||
const workspaceInfo = useConfigStore((s) => s.workspaceInfo);
|
||||
const quickConfig = useConfigStore((s) => s.quickConfig);
|
||||
|
||||
const { messages, currentModel, currentAgent, setCurrentAgent } = useChatStore();
|
||||
const [activeTab, setActiveTab] = useState<'status' | 'files' | 'agent' | 'memory' | 'reflection' | 'autonomy' | 'learning'>('status');
|
||||
const [memoryViewMode, setMemoryViewMode] = useState<'list' | 'graph'>('list');
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useGatewayStore, type Workflow } from '../store/gatewayStore';
|
||||
import { useHandStore } from '../store/handStore';
|
||||
import { useWorkflowStore, type Workflow } from '../store/workflowStore';
|
||||
import { useAgentStore } from '../store/agentStore';
|
||||
import { useConfigStore } from '../store/configStore';
|
||||
import { WorkflowEditor } from './WorkflowEditor';
|
||||
import { WorkflowHistory } from './WorkflowHistory';
|
||||
import { TriggersPanel } from './TriggersPanel';
|
||||
@@ -139,7 +142,14 @@ interface CreateJobModalProps {
|
||||
}
|
||||
|
||||
function CreateJobModal({ isOpen, onClose, onSuccess }: CreateJobModalProps) {
|
||||
const { hands, workflows, clones, createScheduledTask, loadHands, loadWorkflows, loadClones } = useGatewayStore();
|
||||
// Store state - use domain stores
|
||||
const hands = useHandStore((s) => s.hands);
|
||||
const workflows = useWorkflowStore((s) => s.workflows);
|
||||
const clones = useAgentStore((s) => s.clones);
|
||||
const createScheduledTask = useConfigStore((s) => s.createScheduledTask);
|
||||
const loadHands = useHandStore((s) => s.loadHands);
|
||||
const loadWorkflows = useWorkflowStore((s) => s.loadWorkflows);
|
||||
const loadClones = useAgentStore((s) => s.loadClones);
|
||||
const [formData, setFormData] = useState<JobFormData>(initialFormData);
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
@@ -637,15 +647,14 @@ function CreateJobModal({ isOpen, onClose, onSuccess }: CreateJobModalProps) {
|
||||
// === Main SchedulerPanel Component ===
|
||||
|
||||
export function SchedulerPanel() {
|
||||
const {
|
||||
scheduledTasks,
|
||||
loadScheduledTasks,
|
||||
workflows,
|
||||
loadWorkflows,
|
||||
createWorkflow,
|
||||
executeWorkflow,
|
||||
isLoading,
|
||||
} = useGatewayStore();
|
||||
// Store state - use domain stores
|
||||
const scheduledTasks = useConfigStore((s) => s.scheduledTasks);
|
||||
const loadScheduledTasks = useConfigStore((s) => s.loadScheduledTasks);
|
||||
const workflows = useWorkflowStore((s) => s.workflows);
|
||||
const loadWorkflows = useWorkflowStore((s) => s.loadWorkflows);
|
||||
const createWorkflow = useWorkflowStore((s) => s.createWorkflow);
|
||||
const executeWorkflow = useWorkflowStore((s) => s.triggerWorkflow);
|
||||
const isLoading = useHandStore((s) => s.isLoading) || useWorkflowStore((s) => s.isLoading) || useConfigStore((s) => s.isLoading);
|
||||
const [activeTab, setActiveTab] = useState<TabType>('scheduled');
|
||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||
const [isWorkflowEditorOpen, setIsWorkflowEditorOpen] = useState(false);
|
||||
|
||||
@@ -26,8 +26,9 @@ import {
|
||||
Wifi,
|
||||
WifiOff,
|
||||
} from 'lucide-react';
|
||||
import type { SecurityLayer, SecurityStatus } from '../store/gatewayStore';
|
||||
import { useGatewayStore } from '../store/gatewayStore';
|
||||
import type { SecurityLayer, SecurityStatus } from '../store/securityStore';
|
||||
import { useSecurityStore } from '../store/securityStore';
|
||||
import { useConnectionStore } from '../store/connectionStore';
|
||||
|
||||
// OpenFang 16-layer security architecture definitions
|
||||
export const SECURITY_LAYERS: Array<{
|
||||
@@ -522,7 +523,10 @@ interface SecurityStatusPanelProps {
|
||||
}
|
||||
|
||||
export function SecurityStatusPanel({ className = '' }: SecurityStatusPanelProps) {
|
||||
const { securityStatus, securityStatusLoading, loadSecurityStatus, connectionState } = useGatewayStore();
|
||||
const securityStatus = useSecurityStore((s) => s.securityStatus);
|
||||
const securityStatusLoading = useSecurityStore((s) => s.securityStatusLoading);
|
||||
const loadSecurityStatus = useSecurityStore((s) => s.loadSecurityStatus);
|
||||
const connectionState = useConnectionStore((s) => s.connectionState);
|
||||
const [localStatus, setLocalStatus] = useState<SecurityStatus>(getDefaultSecurityStatus());
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Shield, ShieldCheck, ShieldAlert, ShieldX, RefreshCw, Loader2, AlertCircle } from 'lucide-react';
|
||||
import { useGatewayStore } from '../store/gatewayStore';
|
||||
import { useConnectionStore } from '../store/connectionStore';
|
||||
import { useSecurityStore } from '../store/securityStore';
|
||||
|
||||
// OpenFang 16-layer security architecture names (Chinese)
|
||||
const SECURITY_LAYER_NAMES: Record<string, string> = {
|
||||
@@ -75,13 +76,11 @@ function getSecurityLabel(level: 'critical' | 'high' | 'medium' | 'low') {
|
||||
}
|
||||
|
||||
export function SecurityStatus() {
|
||||
const {
|
||||
connectionState,
|
||||
securityStatus,
|
||||
securityStatusLoading,
|
||||
securityStatusError,
|
||||
loadSecurityStatus,
|
||||
} = useGatewayStore();
|
||||
const connectionState = useConnectionStore((s) => s.connectionState);
|
||||
const securityStatus = useSecurityStore((s) => s.securityStatus);
|
||||
const securityStatusLoading = useSecurityStore((s) => s.securityStatusLoading);
|
||||
const securityStatusError = useSecurityStore((s) => s.securityStatusError);
|
||||
const loadSecurityStatus = useSecurityStore((s) => s.loadSecurityStatus);
|
||||
const connected = connectionState === 'connected';
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useGatewayStore } from '../../store/gatewayStore';
|
||||
import { useConnectionStore } from '../../store/connectionStore';
|
||||
import { useConfigStore } from '../../store/configStore';
|
||||
import { useChatStore } from '../../store/chatStore';
|
||||
import { getStoredGatewayToken, setStoredGatewayToken } from '../../lib/gateway-client';
|
||||
import { silentErrorHandler } from '../../lib/error-utils';
|
||||
|
||||
export function General() {
|
||||
const { connectionState, gatewayVersion, error, connect, disconnect, quickConfig, saveQuickConfig } = useGatewayStore();
|
||||
const { currentModel } = useChatStore();
|
||||
const connectionState = useConnectionStore((s) => s.connectionState);
|
||||
const gatewayVersion = useConnectionStore((s) => s.gatewayVersion);
|
||||
const error = useConnectionStore((s) => s.error);
|
||||
const connect = useConnectionStore((s) => s.connect);
|
||||
const disconnect = useConnectionStore((s) => s.disconnect);
|
||||
const quickConfig = useConfigStore((s) => s.quickConfig);
|
||||
const saveQuickConfig = useConfigStore((s) => s.saveQuickConfig);
|
||||
const currentModel = useChatStore((s) => s.currentModel);
|
||||
const [theme, setTheme] = useState<'light' | 'dark'>(quickConfig.theme || 'light');
|
||||
const [autoStart, setAutoStart] = useState(quickConfig.autoStart ?? false);
|
||||
const [showToolCalls, setShowToolCalls] = useState(quickConfig.showToolCalls ?? false);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Radio, RefreshCw, MessageCircle, Settings2 } from 'lucide-react';
|
||||
import { useGatewayStore } from '../../store/gatewayStore';
|
||||
import { useConnectionStore } from '../../store/connectionStore';
|
||||
import { useConfigStore } from '../../store/configStore';
|
||||
import { useAgentStore } from '../../store/agentStore';
|
||||
|
||||
const CHANNEL_ICONS: Record<string, string> = {
|
||||
feishu: '飞',
|
||||
@@ -9,7 +11,10 @@ const CHANNEL_ICONS: Record<string, string> = {
|
||||
};
|
||||
|
||||
export function IMChannels() {
|
||||
const { channels, connectionState, loadChannels, loadPluginStatus } = useGatewayStore();
|
||||
const channels = useConfigStore((s) => s.channels);
|
||||
const loadChannels = useConfigStore((s) => s.loadChannels);
|
||||
const connectionState = useConnectionStore((s) => s.connectionState);
|
||||
const loadPluginStatus = useAgentStore((s) => s.loadPluginStatus);
|
||||
|
||||
const connected = connectionState === 'connected';
|
||||
const loading = connectionState === 'connecting' || connectionState === 'reconnecting' || connectionState === 'handshaking';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { getStoredGatewayToken, getStoredGatewayUrl } from '../../lib/gateway-client';
|
||||
import { useGatewayStore } from '../../store/gatewayStore';
|
||||
import { useConnectionStore } from '../../store/connectionStore';
|
||||
import { useConfigStore } from '../../store/configStore';
|
||||
import { useChatStore } from '../../store/chatStore';
|
||||
import { silentErrorHandler } from '../../lib/error-utils';
|
||||
import { Plus, Pencil, Trash2, Star, Eye, EyeOff, AlertCircle, X } from 'lucide-react';
|
||||
@@ -53,7 +54,11 @@ function saveCustomModels(models: CustomModel[]): void {
|
||||
}
|
||||
|
||||
export function ModelsAPI() {
|
||||
const { connectionState, connect, disconnect, quickConfig, loadModels } = useGatewayStore();
|
||||
const connectionState = useConnectionStore((s) => s.connectionState);
|
||||
const connect = useConnectionStore((s) => s.connect);
|
||||
const disconnect = useConnectionStore((s) => s.disconnect);
|
||||
const quickConfig = useConfigStore((s) => s.quickConfig);
|
||||
const loadModels = useConfigStore((s) => s.loadModels);
|
||||
const { currentModel, setCurrentModel } = useChatStore();
|
||||
const [gatewayUrl, setGatewayUrl] = useState(getStoredGatewayUrl());
|
||||
const [gatewayToken, setGatewayToken] = useState(quickConfig.gatewayToken || getStoredGatewayToken());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { useGatewayStore } from '../../store/gatewayStore';
|
||||
import { useSecurityStore } from '../../store/securityStore';
|
||||
import {
|
||||
Settings as SettingsIcon,
|
||||
BarChart3,
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
HelpCircle,
|
||||
ClipboardList,
|
||||
Clock,
|
||||
Heart,
|
||||
} from 'lucide-react';
|
||||
import { silentErrorHandler } from '../../lib/error-utils';
|
||||
import { General } from './General';
|
||||
@@ -31,6 +32,7 @@ import { AuditLogsPanel } from '../AuditLogsPanel';
|
||||
import { SecurityStatus } from '../SecurityStatus';
|
||||
import { SecurityLayersPanel } from '../SecurityLayersPanel';
|
||||
import { TaskList } from '../TaskList';
|
||||
import { HeartbeatConfig } from '../HeartbeatConfig';
|
||||
|
||||
interface SettingsLayoutProps {
|
||||
onBack: () => void;
|
||||
@@ -49,6 +51,7 @@ type SettingsPage =
|
||||
| 'security'
|
||||
| 'audit'
|
||||
| 'tasks'
|
||||
| 'heartbeat'
|
||||
| 'feedback'
|
||||
| 'about';
|
||||
|
||||
@@ -65,13 +68,14 @@ const menuItems: { id: SettingsPage; label: string; icon: React.ReactNode }[] =
|
||||
{ id: 'security', label: '安全状态', icon: <Shield className="w-4 h-4" /> },
|
||||
{ id: 'audit', label: '审计日志', icon: <ClipboardList className="w-4 h-4" /> },
|
||||
{ id: 'tasks', label: '定时任务', icon: <Clock className="w-4 h-4" /> },
|
||||
{ id: 'heartbeat', label: '心跳配置', icon: <Heart className="w-4 h-4" /> },
|
||||
{ id: 'feedback', label: '提交反馈', icon: <HelpCircle className="w-4 h-4" /> },
|
||||
{ id: 'about', label: '关于', icon: <Info className="w-4 h-4" /> },
|
||||
];
|
||||
|
||||
export function SettingsLayout({ onBack }: SettingsLayoutProps) {
|
||||
const [activePage, setActivePage] = useState<SettingsPage>('general');
|
||||
const { securityStatus } = useGatewayStore();
|
||||
const securityStatus = useSecurityStore((s) => s.securityStatus);
|
||||
|
||||
const renderPage = () => {
|
||||
switch (activePage) {
|
||||
@@ -112,6 +116,11 @@ export function SettingsLayout({ onBack }: SettingsLayoutProps) {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
case 'heartbeat': return (
|
||||
<div className="max-w-3xl h-full">
|
||||
<HeartbeatConfig />
|
||||
</div>
|
||||
);
|
||||
case 'feedback': return <Feedback />;
|
||||
case 'about': return <About />;
|
||||
default: return <General />;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useGatewayStore } from '../../store/gatewayStore';
|
||||
import { useConnectionStore } from '../../store/connectionStore';
|
||||
import { useConfigStore } from '../../store/configStore';
|
||||
import { silentErrorHandler } from '../../lib/error-utils';
|
||||
import { Wrench, Zap, FileCode, Globe, Mail, Database, Search, MessageSquare } from 'lucide-react';
|
||||
|
||||
@@ -64,7 +65,11 @@ const SYSTEM_SKILLS = [
|
||||
];
|
||||
|
||||
export function Skills() {
|
||||
const { connectionState, quickConfig, skillsCatalog, loadSkillsCatalog, saveQuickConfig } = useGatewayStore();
|
||||
const connectionState = useConnectionStore((s) => s.connectionState);
|
||||
const quickConfig = useConfigStore((s) => s.quickConfig);
|
||||
const skillsCatalog = useConfigStore((s) => s.skillsCatalog);
|
||||
const loadSkillsCatalog = useConfigStore((s) => s.loadSkillsCatalog);
|
||||
const saveQuickConfig = useConfigStore((s) => s.saveQuickConfig);
|
||||
const connected = connectionState === 'connected';
|
||||
const [extraDir, setExtraDir] = useState('');
|
||||
const [activeFilter, setActiveFilter] = useState<'all' | 'system' | 'builtin' | 'extra'>('all');
|
||||
|
||||
@@ -11,33 +11,30 @@
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import {
|
||||
Search,
|
||||
Package,
|
||||
Check,
|
||||
Plus,
|
||||
Minus,
|
||||
Sparkles,
|
||||
Tag,
|
||||
Layers,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
RefreshCw,
|
||||
Info,
|
||||
} from 'lucide-react';
|
||||
import { useConfigStore, type SkillInfo } from '../store/configStore';
|
||||
import {
|
||||
SkillDiscoveryEngine,
|
||||
type SkillInfo,
|
||||
type SkillSuggestion,
|
||||
} from '../lib/skill-discovery';
|
||||
adaptSkillsCatalog,
|
||||
type SkillDisplay,
|
||||
} from '../lib/skill-adapter';
|
||||
|
||||
// === Types ===
|
||||
|
||||
interface SkillMarketProps {
|
||||
className?: string;
|
||||
onSkillInstall?: (skill: SkillInfo) => void;
|
||||
onSkillUninstall?: (skill: SkillInfo) => void;
|
||||
onSkillInstall?: (skill: SkillDisplay) => void;
|
||||
onSkillUninstall?: (skill: SkillDisplay) => void;
|
||||
}
|
||||
|
||||
type CategoryFilter = 'all' | 'development' | 'security' | 'analytics' | 'content' | 'ops' | 'management' | 'testing' | 'business' | 'marketing';
|
||||
@@ -80,7 +77,7 @@ function SkillCard({
|
||||
onInstall,
|
||||
onUninstall,
|
||||
}: {
|
||||
skill: SkillInfo;
|
||||
skill: SkillDisplay;
|
||||
isExpanded: boolean;
|
||||
onToggle: () => void;
|
||||
onInstall: () => void;
|
||||
@@ -240,35 +237,6 @@ function SkillCard({
|
||||
);
|
||||
}
|
||||
|
||||
function SuggestionCard({ suggestion }: { suggestion: SkillSuggestion }) {
|
||||
const confidencePercent = Math.round(suggestion.confidence * 100);
|
||||
|
||||
return (
|
||||
<div className="p-3 bg-gradient-to-r from-blue-50 to-purple-50 dark:from-blue-900/20 dark:to-purple-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Sparkles className="w-4 h-4 text-blue-500" />
|
||||
<span className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
{suggestion.skill.name}
|
||||
</span>
|
||||
<span className="text-xs text-blue-600 dark:text-blue-400 ml-auto">
|
||||
{confidencePercent}% 匹配
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-600 dark:text-gray-300 mb-2">{suggestion.reason}</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{suggestion.matchedPatterns.map((pattern) => (
|
||||
<span
|
||||
key={pattern}
|
||||
className="px-1.5 py-0.5 text-xs bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400 rounded"
|
||||
>
|
||||
{pattern}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// === Main Component ===
|
||||
|
||||
export function SkillMarket({
|
||||
@@ -276,19 +244,23 @@ export function SkillMarket({
|
||||
onSkillInstall,
|
||||
onSkillUninstall,
|
||||
}: SkillMarketProps) {
|
||||
const [engine] = useState(() => new SkillDiscoveryEngine());
|
||||
const [skills, setSkills] = useState<SkillInfo[]>([]);
|
||||
// Use configStore instead of SkillDiscoveryEngine
|
||||
const skillsCatalog = useConfigStore((s) => s.skillsCatalog);
|
||||
const loadSkillsCatalog = useConfigStore((s) => s.loadSkillsCatalog);
|
||||
const updateSkill = useConfigStore((s) => s.updateSkill);
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [categoryFilter, setCategoryFilter] = useState<CategoryFilter>('all');
|
||||
const [expandedSkillId, setExpandedSkillId] = useState<string | null>(null);
|
||||
const [suggestions, setSuggestions] = useState<SkillSuggestion[]>([]);
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
|
||||
// Load skills
|
||||
// Adapt skills to display format
|
||||
const skills = useMemo(() => adaptSkillsCatalog(skillsCatalog), [skillsCatalog]);
|
||||
|
||||
// Load skills on mount
|
||||
useEffect(() => {
|
||||
const allSkills = engine.getAllSkills();
|
||||
setSkills(allSkills);
|
||||
}, [engine]);
|
||||
loadSkillsCatalog();
|
||||
}, [loadSkillsCatalog]);
|
||||
|
||||
// Filter skills
|
||||
const filteredSkills = useMemo(() => {
|
||||
@@ -301,13 +273,17 @@ export function SkillMarket({
|
||||
|
||||
// Search filter
|
||||
if (searchQuery.trim()) {
|
||||
const searchResult = engine.searchSkills(searchQuery);
|
||||
const matchingIds = new Set(searchResult.results.map((s) => s.id));
|
||||
result = result.filter((s) => matchingIds.has(s.id));
|
||||
const queryLower = searchQuery.toLowerCase();
|
||||
result = result.filter((s) =>
|
||||
s.name.toLowerCase().includes(queryLower) ||
|
||||
s.description.toLowerCase().includes(queryLower) ||
|
||||
s.triggers.some((t) => t.toLowerCase().includes(queryLower)) ||
|
||||
s.capabilities.some((c) => c.toLowerCase().includes(queryLower))
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}, [skills, categoryFilter, searchQuery, engine]);
|
||||
}, [skills, categoryFilter, searchQuery]);
|
||||
|
||||
// Get categories from skills
|
||||
const categories = useMemo(() => {
|
||||
@@ -323,44 +299,31 @@ export function SkillMarket({
|
||||
|
||||
const handleRefresh = useCallback(async () => {
|
||||
setIsRefreshing(true);
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
// engine.refreshIndex doesn't exist - skip
|
||||
setSkills(engine.getAllSkills());
|
||||
await loadSkillsCatalog();
|
||||
setIsRefreshing(false);
|
||||
}, [engine]);
|
||||
}, [loadSkillsCatalog]);
|
||||
|
||||
const handleInstall = useCallback(
|
||||
(skill: SkillInfo) => {
|
||||
// Install skill - update local state
|
||||
setSkills((prev) => prev.map(s => ({ ...s, installed: true })));
|
||||
onSkillInstall?.(skill);
|
||||
},
|
||||
[onSkillInstall]
|
||||
async (skill: SkillDisplay) => {
|
||||
// Update skill via configStore (persists to backend)
|
||||
await updateSkill(skill.id, { enabled: true });
|
||||
onSkillInstall?.(skill);
|
||||
},
|
||||
[updateSkill, onSkillInstall]
|
||||
);
|
||||
|
||||
const handleUninstall = useCallback(
|
||||
(skill: SkillInfo) => {
|
||||
// Uninstall skill - update local state
|
||||
setSkills((prev) => prev.map(s => ({ ...s, installed: false })));
|
||||
onSkillUninstall?.(skill);
|
||||
async (skill: SkillDisplay) => {
|
||||
// Update skill via configStore (persists to backend)
|
||||
await updateSkill(skill.id, { enabled: false });
|
||||
onSkillUninstall?.(skill);
|
||||
},
|
||||
[onSkillUninstall]
|
||||
[updateSkill, onSkillUninstall]
|
||||
);
|
||||
|
||||
const handleSearch = useCallback(
|
||||
async (query: string) => {
|
||||
setSearchQuery(query);
|
||||
if (query.trim()) {
|
||||
// Get suggestions based on search
|
||||
const mockConversation = [{ role: 'user' as const, content: query }];
|
||||
const newSuggestions = await engine.suggestSkills(mockConversation, 'default', 3);
|
||||
setSuggestions(newSuggestions.slice(0, 3));
|
||||
} else {
|
||||
setSuggestions([]);
|
||||
}
|
||||
},
|
||||
[engine]
|
||||
);
|
||||
const handleSearch = useCallback((query: string) => {
|
||||
setSearchQuery(query);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col h-full ${className}`}>
|
||||
@@ -405,25 +368,8 @@ export function SkillMarket({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Suggestions */}
|
||||
<AnimatePresence>
|
||||
{suggestions.length > 0 && (
|
||||
<motion.div
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{ height: 'auto', opacity: 1 }}
|
||||
exit={{ height: 0, opacity: 0 }}
|
||||
className="mt-3 space-y-2"
|
||||
>
|
||||
<h4 className="text-xs font-medium text-gray-500 dark:text-gray-400 flex items-center gap-1">
|
||||
<Info className="w-3 h-3" />
|
||||
推荐技能
|
||||
</h4>
|
||||
{suggestions.map((suggestion) => (
|
||||
<SuggestionCard key={suggestion.skill.id} suggestion={suggestion} />
|
||||
))}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
{/* Suggestions - placeholder for future AI-powered recommendations */}
|
||||
|
||||
</div>
|
||||
|
||||
{/* Category Filter */}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useGatewayStore } from '../store/gatewayStore';
|
||||
import { useConnectionStore } from '../store/connectionStore';
|
||||
import { useConfigStore } from '../store/configStore';
|
||||
import { Clock, RefreshCw, Play, Pause, AlertCircle, CheckCircle2 } from 'lucide-react';
|
||||
|
||||
const STATUS_CONFIG: Record<string, { icon: typeof Play; color: string; label: string }> = {
|
||||
@@ -10,7 +11,9 @@ const STATUS_CONFIG: Record<string, { icon: typeof Play; color: string; label: s
|
||||
};
|
||||
|
||||
export function TaskList() {
|
||||
const { scheduledTasks, connectionState, loadScheduledTasks } = useGatewayStore();
|
||||
const scheduledTasks = useConfigStore((s) => s.scheduledTasks);
|
||||
const connectionState = useConnectionStore((s) => s.connectionState);
|
||||
const loadScheduledTasks = useConfigStore((s) => s.loadScheduledTasks);
|
||||
|
||||
const connected = connectionState === 'connected';
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTeamStore } from '../store/teamStore';
|
||||
import { useGatewayStore } from '../store/gatewayStore';
|
||||
import { useAgentStore } from '../store/agentStore';
|
||||
import { useChatStore } from '../store/chatStore';
|
||||
import { Users, Plus, Activity, CheckCircle, AlertTriangle, X, Bot } from 'lucide-react';
|
||||
import type { TeamMemberRole } from '../types/team';
|
||||
@@ -20,7 +20,7 @@ interface TeamListProps {
|
||||
|
||||
export function TeamList({ onSelectTeam, selectedTeamId }: TeamListProps) {
|
||||
const { teams, loadTeams, setActiveTeam, createTeam, isLoading } = useTeamStore();
|
||||
const { clones } = useGatewayStore();
|
||||
const clones = useAgentStore((s) => s.clones);
|
||||
const { agents } = useChatStore();
|
||||
const [showCreateModal, setShowCreateModal] = useState(false);
|
||||
const [teamName, setTeamName] = useState('');
|
||||
|
||||
@@ -107,9 +107,9 @@ function TriggerCard({ trigger, onToggle, onDelete, isToggling, isDeleting }: Tr
|
||||
export function TriggersPanel() {
|
||||
const triggers = useHandStore((s) => s.triggers);
|
||||
const loadTriggers = useHandStore((s) => s.loadTriggers);
|
||||
const updateTrigger = useHandStore((s) => s.updateTrigger);
|
||||
const deleteTrigger = useHandStore((s) => s.deleteTrigger);
|
||||
const isLoading = useHandStore((s) => s.isLoading);
|
||||
const client = useHandStore((s) => s.client);
|
||||
const [togglingTrigger, setTogglingTrigger] = useState<string | null>(null);
|
||||
const [deletingTrigger, setDeletingTrigger] = useState<string | null>(null);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
@@ -122,14 +122,14 @@ export function TriggersPanel() {
|
||||
const handleToggle = useCallback(async (id: string, enabled: boolean) => {
|
||||
setTogglingTrigger(id);
|
||||
try {
|
||||
await client.request('triggers.toggle', { id, enabled });
|
||||
await updateTrigger(id, { enabled });
|
||||
await loadTriggers();
|
||||
} catch (error) {
|
||||
console.error('Failed to toggle trigger:', error);
|
||||
} finally {
|
||||
setTogglingTrigger(null);
|
||||
}
|
||||
}, [client, loadTriggers]);
|
||||
}, [updateTrigger, loadTriggers]);
|
||||
|
||||
const handleDelete = useCallback(async (id: string) => {
|
||||
setDeletingTrigger(id);
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useGatewayStore, type Hand, type Workflow } from '../store/gatewayStore';
|
||||
import { useHandStore, type Hand } from '../store/handStore';
|
||||
import type { Workflow } from '../store/workflowStore';
|
||||
import {
|
||||
X,
|
||||
Plus,
|
||||
@@ -199,7 +200,8 @@ function StepEditor({ step, hands, index, onUpdate, onRemove, onMoveUp, onMoveDo
|
||||
// === Main WorkflowEditor Component ===
|
||||
|
||||
export function WorkflowEditor({ workflow, isOpen, onClose, onSave, isSaving }: WorkflowEditorProps) {
|
||||
const { hands, loadHands } = useGatewayStore();
|
||||
const hands = useHandStore((s) => s.hands);
|
||||
const loadHands = useHandStore((s) => s.loadHands);
|
||||
const [name, setName] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [steps, setSteps] = useState<WorkflowStep[]>([]);
|
||||
|
||||
Reference in New Issue
Block a user