feat(phase2): complete P1 tasks - Channels, Triggers, Skills CRUD and UI enhancements

Phase 2 P1 Tasks Completed:

API Layer (gateway-client.ts, gatewayStore.ts):
- Add Channels CRUD: getChannel, createChannel, updateChannel, deleteChannel
- Add Triggers CRUD: getTrigger, createTrigger, updateTrigger, deleteTrigger
- Add Skills CRUD: getSkill, createSkill, updateSkill, deleteSkill
- Add Scheduled Tasks API: createScheduledTask, deleteScheduledTask, toggleScheduledTask
- Add loadModels action for dynamic model list

UI Components:
- ModelsAPI.tsx: Dynamic model loading from API with loading/error states
- SchedulerPanel.tsx: Full CreateJobModal with cron/interval/once scheduling
- SecurityStatus.tsx: Loading states, error handling, retry functionality
- WorkflowEditor.tsx: New workflow creation/editing modal (new file)
- WorkflowHistory.tsx: Workflow execution history viewer (new file)
- WorkflowList.tsx: Integrated editor and history access

Configuration:
- Add 4 Hands TOML configs: clip, collector, predictor, twitter

Documentation (SYSTEM_ANALYSIS.md):
- Update API coverage: 65% → 89% (53/62 endpoints)
- Update UI completion: 85% → 92%
- Mark Phase 2 P1 tasks as completed
- Update technical debt cleanup status

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-03-15 01:38:34 +08:00
parent 1f9b6553fc
commit 5599c1a4db
15 changed files with 3216 additions and 296 deletions

View File

@@ -9,6 +9,8 @@
import { useState, useEffect, useCallback } from 'react';
import { useGatewayStore } from '../store/gatewayStore';
import type { Workflow } from '../store/gatewayStore';
import { WorkflowEditor } from './WorkflowEditor';
import { WorkflowHistory } from './WorkflowHistory';
import {
Play,
Edit,
@@ -20,6 +22,7 @@ import {
RefreshCw,
Loader2,
X,
AlertTriangle,
} from 'lucide-react';
// === View Toggle Types ===
@@ -141,9 +144,10 @@ interface WorkflowRowProps {
onDelete: (workflow: Workflow) => void;
onHistory: (workflow: Workflow) => void;
isExecuting: boolean;
isDeleting: boolean;
}
function WorkflowRow({ workflow, onExecute, onEdit, onDelete, onHistory, isExecuting }: WorkflowRowProps) {
function WorkflowRow({ workflow, onExecute, onEdit, onDelete, onHistory, isExecuting, isDeleting }: WorkflowRowProps) {
// Format created date if available
const createdDate = workflow.createdAt
? new Date(workflow.createdAt).toLocaleDateString('zh-CN')
@@ -213,10 +217,15 @@ function WorkflowRow({ workflow, onExecute, onEdit, onDelete, onHistory, isExecu
</button>
<button
onClick={() => onDelete(workflow)}
className="p-1.5 text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-md"
title="Delete"
disabled={isDeleting}
className="p-1.5 text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-md disabled:opacity-50 disabled:cursor-not-allowed"
title="删除"
>
<Trash2 className="w-4 h-4" />
{isDeleting ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<Trash2 className="w-4 h-4" />
)}
</button>
</div>
</td>
@@ -227,11 +236,16 @@ function WorkflowRow({ workflow, onExecute, onEdit, onDelete, onHistory, isExecu
// === Main WorkflowList Component ===
export function WorkflowList() {
const { workflows, loadWorkflows, executeWorkflow, isLoading } = useGatewayStore();
const { workflows, loadWorkflows, executeWorkflow, deleteWorkflow, createWorkflow, updateWorkflow, isLoading } = useGatewayStore();
const [viewMode, setViewMode] = useState<ViewMode>('list');
const [executingWorkflowId, setExecutingWorkflowId] = useState<string | null>(null);
const [deletingWorkflowId, setDeletingWorkflowId] = useState<string | null>(null);
const [selectedWorkflow, setSelectedWorkflow] = useState<Workflow | null>(null);
const [showExecuteModal, setShowExecuteModal] = useState(false);
const [showEditor, setShowEditor] = useState(false);
const [showHistory, setShowHistory] = useState(false);
const [editingWorkflow, setEditingWorkflow] = useState<Workflow | null>(null);
const [isSaving, setIsSaving] = useState(false);
useEffect(() => {
loadWorkflows();
@@ -252,28 +266,61 @@ export function WorkflowList() {
}, []);
const handleEdit = useCallback((workflow: Workflow) => {
// TODO: Implement workflow editor
console.log('Edit workflow:', workflow.id);
alert('工作流编辑器即将推出!');
setEditingWorkflow(workflow);
setShowEditor(true);
}, []);
const handleDelete = useCallback((workflow: Workflow) => {
// TODO: Implement workflow deletion
console.log('Delete workflow:', workflow.id);
if (confirm(`确定要删除 "${workflow.name}" 吗?`)) {
alert('工作流删除功能即将推出!');
const handleDelete = useCallback(async (workflow: Workflow) => {
if (confirm(`确定要删除 "${workflow.name}" 吗?此操作不可撤销。`)) {
setDeletingWorkflowId(workflow.id);
try {
await deleteWorkflow(workflow.id);
// The store will update the workflows list automatically
} catch (error) {
console.error('Failed to delete workflow:', error);
alert(`删除工作流失败: ${error instanceof Error ? error.message : '未知错误'}`);
} finally {
setDeletingWorkflowId(null);
}
}
}, []);
}, [deleteWorkflow]);
const handleHistory = useCallback((workflow: Workflow) => {
// TODO: Implement workflow history view
console.log('View history:', workflow.id);
alert('工作流历史功能即将推出!');
setSelectedWorkflow(workflow);
setShowHistory(true);
}, []);
const handleNewWorkflow = useCallback(() => {
// TODO: Implement new workflow creation
alert('工作流构建器即将推出!');
setEditingWorkflow(null);
setShowEditor(true);
}, []);
const handleSaveWorkflow = useCallback(async (data: {
name: string;
description?: string;
steps: Array<{
handName: string;
name?: string;
params?: Record<string, unknown>;
condition?: string;
}>;
}) => {
setIsSaving(true);
try {
if (editingWorkflow) {
await updateWorkflow(editingWorkflow.id, data);
} else {
await createWorkflow(data);
}
await loadWorkflows();
} finally {
setIsSaving(false);
}
}, [editingWorkflow, createWorkflow, updateWorkflow, loadWorkflows]);
const handleCloseEditor = useCallback(() => {
setShowEditor(false);
setEditingWorkflow(null);
}, []);
const handleCloseModal = useCallback(() => {
@@ -407,6 +454,7 @@ export function WorkflowList() {
onDelete={handleDelete}
onHistory={handleHistory}
isExecuting={executingWorkflowId === workflow.id}
isDeleting={deletingWorkflowId === workflow.id}
/>
))}
</tbody>
@@ -438,6 +486,27 @@ export function WorkflowList() {
isExecuting={executingWorkflowId === selectedWorkflow.id}
/>
)}
{/* Workflow Editor */}
<WorkflowEditor
workflow={editingWorkflow || undefined}
isOpen={showEditor}
onClose={handleCloseEditor}
onSave={handleSaveWorkflow}
isSaving={isSaving}
/>
{/* Workflow History */}
{selectedWorkflow && (
<WorkflowHistory
workflow={selectedWorkflow}
isOpen={showHistory}
onClose={() => {
setShowHistory(false);
setSelectedWorkflow(null);
}}
/>
)}
</div>
);
}