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:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user