重构 API 路由并新增工作流编排功能
后端: - 重构 agents, heartbeats, locks, meetings, resources, roles, workflows 路由 - 新增 orchestrator 和 providers 路由 - 新增 CLI 调用器和流程编排服务 - 添加日志配置和依赖项 前端: - 更新 AgentsPage、SettingsPage、WorkflowPage 页面 - 扩展 api.ts 新增 API 接口 其他: - 清理测试 agent 数据文件 - 新增示例工作流和项目审计报告 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+95
-2
@@ -11,8 +11,23 @@ import type {
|
||||
AgentResourceStatus,
|
||||
} from '../types';
|
||||
|
||||
// API 基础地址
|
||||
const API_BASE = 'http://localhost:8000/api';
|
||||
// API 基础地址:优先读取 localStorage(Settings 页面配置),否则使用默认值
|
||||
function getApiBase(): string {
|
||||
try {
|
||||
const saved = localStorage.getItem('swarm-settings');
|
||||
if (saved) {
|
||||
const settings = JSON.parse(saved);
|
||||
if (settings.apiBaseUrl) {
|
||||
return settings.apiBaseUrl.replace(/\/+$/, '');
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore parse errors
|
||||
}
|
||||
return 'http://localhost:8000/api';
|
||||
}
|
||||
|
||||
const API_BASE = getApiBase();
|
||||
|
||||
// 通用请求函数
|
||||
async function request<T>(
|
||||
@@ -336,6 +351,47 @@ export const roleApi = {
|
||||
}),
|
||||
};
|
||||
|
||||
// ==================== Agent 控制 API ====================
|
||||
|
||||
export const agentControlApi = {
|
||||
// 启动 Agent
|
||||
start: (agentId: string, agentType: string, model?: string) =>
|
||||
request<{ success: boolean; agent_id: string; status: string }>('/agents/control/start', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ agent_id: agentId, agent_type: agentType, model }),
|
||||
}),
|
||||
|
||||
// 停止 Agent
|
||||
stop: (agentId: string) =>
|
||||
request<{ success: boolean; agent_id: string }>('/agents/control/stop', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ agent_id: agentId }),
|
||||
}),
|
||||
|
||||
// 重启 Agent
|
||||
restart: (agentId: string) =>
|
||||
request<{ success: boolean; agent_id: string }>('/agents/control/restart', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ agent_id: agentId }),
|
||||
}),
|
||||
|
||||
// 获取 Agent 运行状态
|
||||
getStatus: (agentId: string) =>
|
||||
request<{ agent_id: string; status: string; pid?: number; uptime?: number }>(`/agents/control/status/${agentId}`),
|
||||
|
||||
// 获取所有运行中的 Agent
|
||||
list: () =>
|
||||
request<{ agents: Array<{ agent_id: string; status: string; pid?: number; agent_type?: string }> }>('/agents/control/list'),
|
||||
|
||||
// 获取进程管理器摘要
|
||||
summary: () =>
|
||||
request<{ total: number; running: number; stopped: number }>('/agents/control/summary'),
|
||||
|
||||
// 健康检查
|
||||
health: () =>
|
||||
request<{ status: string }>('/agents/control/health'),
|
||||
};
|
||||
|
||||
// ==================== 系统 API ====================
|
||||
|
||||
export const systemApi = {
|
||||
@@ -424,9 +480,45 @@ export const humanApi = {
|
||||
}),
|
||||
};
|
||||
|
||||
// ==================== Provider API ====================
|
||||
|
||||
export const providerApi = {
|
||||
list: () =>
|
||||
request<{
|
||||
cli: Array<{
|
||||
id: string;
|
||||
type: string;
|
||||
display_name: string;
|
||||
description: string;
|
||||
installed: boolean;
|
||||
path: string;
|
||||
models: string[];
|
||||
}>;
|
||||
api: Array<{
|
||||
id: string;
|
||||
type: string;
|
||||
display_name: string;
|
||||
env_key: string;
|
||||
configured: boolean;
|
||||
models: string[];
|
||||
}>;
|
||||
}>('/providers'),
|
||||
|
||||
models: () =>
|
||||
request<{
|
||||
models: Array<{
|
||||
value: string;
|
||||
label: string;
|
||||
provider: string;
|
||||
type: string;
|
||||
}>;
|
||||
}>('/providers/models'),
|
||||
};
|
||||
|
||||
// 导出所有 API
|
||||
export const api = {
|
||||
agent: agentApi,
|
||||
agentControl: agentControlApi,
|
||||
lock: lockApi,
|
||||
heartbeat: heartbeatApi,
|
||||
meeting: meetingApi,
|
||||
@@ -435,6 +527,7 @@ export const api = {
|
||||
role: roleApi,
|
||||
system: systemApi,
|
||||
human: humanApi,
|
||||
provider: providerApi,
|
||||
};
|
||||
|
||||
export default api;
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Plus, Users, Activity, Cpu, RefreshCw, Play, Square, Power } from 'lucide-react';
|
||||
import { api } from '../lib/api';
|
||||
import { Plus, Users, Activity, Cpu, RefreshCw, Play, Square, Trash2 } from 'lucide-react';
|
||||
import { api, agentControlApi } from '../lib/api';
|
||||
import type { Agent, AgentState } from '../types';
|
||||
|
||||
interface ModelOption {
|
||||
value: string;
|
||||
label: string;
|
||||
provider: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
// 注册 Agent 模态框
|
||||
function RegisterModal({
|
||||
isOpen,
|
||||
@@ -23,9 +30,21 @@ function RegisterModal({
|
||||
agent_id: '',
|
||||
name: '',
|
||||
role: 'developer',
|
||||
model: 'claude-opus-4.6',
|
||||
model: '',
|
||||
description: '',
|
||||
});
|
||||
const [modelOptions, setModelOptions] = useState<ModelOption[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
api.provider.models().then((data) => {
|
||||
setModelOptions(data.models);
|
||||
if (data.models.length > 0 && !form.model) {
|
||||
setForm((f) => ({ ...f, model: data.models[0].value }));
|
||||
}
|
||||
}).catch(() => {});
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
@@ -167,13 +186,11 @@ function RegisterModal({
|
||||
marginBottom: 6,
|
||||
}}
|
||||
>
|
||||
模型
|
||||
模型 / CLI
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
<select
|
||||
value={form.model}
|
||||
onChange={(e) => setForm({ ...form, model: e.target.value })}
|
||||
placeholder="模型名称"
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '10px 14px',
|
||||
@@ -184,7 +201,17 @@ function RegisterModal({
|
||||
fontSize: 14,
|
||||
outline: 'none',
|
||||
}}
|
||||
/>
|
||||
>
|
||||
{modelOptions.length > 0 ? (
|
||||
modelOptions.map((m) => (
|
||||
<option key={m.value} value={m.value}>
|
||||
{m.label}
|
||||
</option>
|
||||
))
|
||||
) : (
|
||||
<option value="">加载中...</option>
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -236,14 +263,14 @@ function RegisterModal({
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (form.agent_id && form.name) {
|
||||
if (form.agent_id && form.name && form.model) {
|
||||
onSubmit(form);
|
||||
onClose();
|
||||
setForm({
|
||||
agent_id: '',
|
||||
name: '',
|
||||
role: 'developer',
|
||||
model: 'claude-opus-4.6',
|
||||
model: modelOptions.length > 0 ? modelOptions[0].value : '',
|
||||
description: '',
|
||||
});
|
||||
}
|
||||
@@ -504,9 +531,11 @@ function AgentDetailPanel({
|
||||
interface RunningAgent {
|
||||
agent_id: string;
|
||||
status: string;
|
||||
is_alive: boolean;
|
||||
uptime: number | null;
|
||||
restart_count: number;
|
||||
is_alive?: boolean;
|
||||
uptime?: number | null;
|
||||
restart_count?: number;
|
||||
pid?: number;
|
||||
agent_type?: string;
|
||||
}
|
||||
|
||||
export function AgentsPage() {
|
||||
@@ -518,9 +547,6 @@ export function AgentsPage() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// API 基础 URL
|
||||
const API_BASE = 'http://localhost:8000/api';
|
||||
|
||||
// 加载 Agent 列表
|
||||
const loadAgents = async () => {
|
||||
try {
|
||||
@@ -538,15 +564,12 @@ export function AgentsPage() {
|
||||
// 加载运行中的 Agent
|
||||
const loadRunningAgents = async () => {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/agents/control/list`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
const runningMap: Record<string, RunningAgent> = {};
|
||||
data.forEach((agent: RunningAgent) => {
|
||||
runningMap[agent.agent_id] = agent;
|
||||
});
|
||||
setRunningAgents(runningMap);
|
||||
}
|
||||
const data = await agentControlApi.list();
|
||||
const runningMap: Record<string, RunningAgent> = {};
|
||||
(data.agents || []).forEach((agent: RunningAgent) => {
|
||||
runningMap[agent.agent_id] = agent;
|
||||
});
|
||||
setRunningAgents(runningMap);
|
||||
} catch (err) {
|
||||
console.error('加载运行状态失败:', err);
|
||||
}
|
||||
@@ -555,24 +578,8 @@ export function AgentsPage() {
|
||||
// 启动 Agent
|
||||
const startAgent = async (agentId: string, agent: Agent) => {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/agents/control/start`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
agent_id: agentId,
|
||||
name: agent.name,
|
||||
role: agent.role,
|
||||
model: agent.model,
|
||||
agent_type: 'native_llm'
|
||||
})
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
await loadRunningAgents();
|
||||
} else {
|
||||
const data = await res.json();
|
||||
alert(`启动失败: ${data.message || '未知错误'}`);
|
||||
}
|
||||
await agentControlApi.start(agentId, 'native_llm', agent.model);
|
||||
await loadRunningAgents();
|
||||
} catch (err) {
|
||||
alert(`启动失败: ${err instanceof Error ? err.message : '未知错误'}`);
|
||||
}
|
||||
@@ -581,25 +588,24 @@ export function AgentsPage() {
|
||||
// 停止 Agent
|
||||
const stopAgent = async (agentId: string) => {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/agents/control/stop`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
agent_id: agentId,
|
||||
graceful: true
|
||||
})
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
await loadRunningAgents();
|
||||
} else {
|
||||
alert('停止失败');
|
||||
}
|
||||
await agentControlApi.stop(agentId);
|
||||
await loadRunningAgents();
|
||||
} catch (err) {
|
||||
alert(`停止失败: ${err instanceof Error ? err.message : '未知错误'}`);
|
||||
}
|
||||
};
|
||||
|
||||
// 删除 Agent
|
||||
const deleteAgent = async (agentId: string) => {
|
||||
if (!confirm(`确认删除 Agent "${agentId}" ?`)) return;
|
||||
try {
|
||||
await fetch(`http://localhost:8000/api/agents/${agentId}`, { method: 'DELETE' });
|
||||
loadAgents();
|
||||
} catch (err) {
|
||||
alert(`删除失败: ${err instanceof Error ? err.message : '未知错误'}`);
|
||||
}
|
||||
};
|
||||
|
||||
// 加载 Agent 状态
|
||||
const loadAgentState = async (agentId: string) => {
|
||||
try {
|
||||
@@ -883,7 +889,7 @@ export function AgentsPage() {
|
||||
<p style={{ fontSize: 12, color: 'rgba(255, 255, 255, 0.3)', margin: '4px 0 0 0' }}>
|
||||
创建于: {new Date(agent.created_at).toLocaleDateString()}
|
||||
</p>
|
||||
{/* 启动/停止按钮 */}
|
||||
{/* 启动/停止/删除按钮 */}
|
||||
<div style={{ display: 'flex', gap: 8, marginTop: 12 }}>
|
||||
{runningAgents[agent.agent_id] ? (
|
||||
<button
|
||||
@@ -934,6 +940,27 @@ export function AgentsPage() {
|
||||
启动
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
deleteAgent(agent.agent_id);
|
||||
}}
|
||||
title="删除 Agent"
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '8px 10px',
|
||||
background: 'rgba(255,255,255,0.03)',
|
||||
border: '1px solid rgba(255,255,255,0.08)',
|
||||
borderRadius: 6,
|
||||
color: 'rgba(255,255,255,0.3)',
|
||||
fontSize: 12,
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
</button>
|
||||
</div>
|
||||
{/* 显示运行时长 */}
|
||||
{runningAgents[agent.agent_id]?.uptime && (
|
||||
|
||||
@@ -9,9 +9,31 @@ import {
|
||||
Database,
|
||||
FileJson,
|
||||
FolderOpen,
|
||||
Terminal,
|
||||
Key,
|
||||
Cpu,
|
||||
} from 'lucide-react';
|
||||
import { api } from '../lib/api';
|
||||
|
||||
interface CliProvider {
|
||||
id: string;
|
||||
type: string;
|
||||
display_name: string;
|
||||
description: string;
|
||||
installed: boolean;
|
||||
path: string;
|
||||
models: string[];
|
||||
}
|
||||
|
||||
interface ApiProvider {
|
||||
id: string;
|
||||
type: string;
|
||||
display_name: string;
|
||||
env_key: string;
|
||||
configured: boolean;
|
||||
models: string[];
|
||||
}
|
||||
|
||||
interface Config {
|
||||
apiBaseUrl: string;
|
||||
refreshInterval: number;
|
||||
@@ -32,6 +54,8 @@ export function SettingsPage() {
|
||||
const [testing, setTesting] = useState(false);
|
||||
const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null);
|
||||
const [backendInfo, setBackendInfo] = useState<{ status: string; version: string } | null>(null);
|
||||
const [cliProviders, setCliProviders] = useState<CliProvider[]>([]);
|
||||
const [apiProviders, setApiProviders] = useState<ApiProvider[]>([]);
|
||||
|
||||
// 从 localStorage 加载配置
|
||||
useEffect(() => {
|
||||
@@ -43,8 +67,19 @@ export function SettingsPage() {
|
||||
// 忽略解析错误
|
||||
}
|
||||
}
|
||||
loadProviders();
|
||||
}, []);
|
||||
|
||||
const loadProviders = async () => {
|
||||
try {
|
||||
const data = await api.provider.list();
|
||||
setCliProviders(data.cli);
|
||||
setApiProviders(data.api);
|
||||
} catch {
|
||||
// 后端未运行时忽略
|
||||
}
|
||||
};
|
||||
|
||||
// 保存配置
|
||||
const handleSave = () => {
|
||||
localStorage.setItem('swarm-config', JSON.stringify(config));
|
||||
@@ -197,6 +232,192 @@ export function SettingsPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* AI Provider 配置 */}
|
||||
<div
|
||||
style={{
|
||||
padding: 24,
|
||||
background: 'rgba(17, 24, 39, 0.7)',
|
||||
borderRadius: 12,
|
||||
border: '1px solid rgba(0, 240, 255, 0.1)',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 20 }}>
|
||||
<div
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 10,
|
||||
background: 'rgba(139, 92, 246, 0.1)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: '#8b5cf6',
|
||||
}}
|
||||
>
|
||||
<Cpu size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 style={{ fontSize: 16, fontWeight: 600, color: '#fff', margin: 0 }}>
|
||||
AI Provider
|
||||
</h3>
|
||||
<p style={{ fontSize: 12, color: 'rgba(255, 255, 255, 0.4)', margin: '4px 0 0 0' }}>
|
||||
可用的 CLI 工具和 API 供应商
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={loadProviders}
|
||||
style={{
|
||||
marginLeft: 'auto',
|
||||
padding: '6px 12px',
|
||||
background: 'rgba(255,255,255,0.05)',
|
||||
border: '1px solid rgba(255,255,255,0.1)',
|
||||
borderRadius: 6,
|
||||
color: 'rgba(255,255,255,0.6)',
|
||||
fontSize: 12,
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
<RefreshCw size={12} />
|
||||
刷新
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* CLI 工具 */}
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 10 }}>
|
||||
<Terminal size={14} color="rgba(255,255,255,0.5)" />
|
||||
<span style={{ fontSize: 13, color: 'rgba(255,255,255,0.6)', fontWeight: 500 }}>
|
||||
CLI 工具
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
{cliProviders.map((cli) => (
|
||||
<div
|
||||
key={cli.id}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
padding: '10px 14px',
|
||||
background: cli.installed ? 'rgba(0, 255, 157, 0.06)' : 'rgba(255,255,255,0.02)',
|
||||
border: `1px solid ${cli.installed ? 'rgba(0, 255, 157, 0.15)' : 'rgba(255,255,255,0.06)'}`,
|
||||
borderRadius: 8,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: '50%',
|
||||
background: cli.installed ? '#00ff9d' : '#666',
|
||||
boxShadow: cli.installed ? '0 0 6px #00ff9d' : 'none',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
<div style={{ flex: 1 }}>
|
||||
<span style={{ fontSize: 14, color: '#fff', fontWeight: 500 }}>
|
||||
{cli.display_name}
|
||||
</span>
|
||||
<span style={{ fontSize: 11, color: 'rgba(255,255,255,0.3)', marginLeft: 8 }}>
|
||||
{cli.description}
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 11,
|
||||
padding: '3px 8px',
|
||||
borderRadius: 4,
|
||||
background: cli.installed ? '#00ff9d20' : '#ff006e15',
|
||||
color: cli.installed ? '#00ff9d' : '#ff006e',
|
||||
}}
|
||||
>
|
||||
{cli.installed ? '已安装' : '未安装'}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{cliProviders.length === 0 && (
|
||||
<p style={{ fontSize: 12, color: 'rgba(255,255,255,0.3)', margin: 0 }}>
|
||||
连接后端后显示 CLI 状态
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* API Provider */}
|
||||
<div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 10 }}>
|
||||
<Key size={14} color="rgba(255,255,255,0.5)" />
|
||||
<span style={{ fontSize: 13, color: 'rgba(255,255,255,0.6)', fontWeight: 500 }}>
|
||||
API 供应商
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
{apiProviders.map((prov) => (
|
||||
<div
|
||||
key={prov.id}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
padding: '10px 14px',
|
||||
background: prov.configured ? 'rgba(0, 240, 255, 0.06)' : 'rgba(255,255,255,0.02)',
|
||||
border: `1px solid ${prov.configured ? 'rgba(0, 240, 255, 0.15)' : 'rgba(255,255,255,0.06)'}`,
|
||||
borderRadius: 8,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: '50%',
|
||||
background: prov.configured ? '#00f0ff' : '#666',
|
||||
boxShadow: prov.configured ? '0 0 6px #00f0ff' : 'none',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
<div style={{ flex: 1 }}>
|
||||
<span style={{ fontSize: 14, color: '#fff', fontWeight: 500 }}>
|
||||
{prov.display_name}
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 11,
|
||||
color: 'rgba(255,255,255,0.3)',
|
||||
marginLeft: 8,
|
||||
fontFamily: 'monospace',
|
||||
}}
|
||||
>
|
||||
{prov.env_key}
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 11,
|
||||
padding: '3px 8px',
|
||||
borderRadius: 4,
|
||||
background: prov.configured ? '#00f0ff20' : '#ff950015',
|
||||
color: prov.configured ? '#00f0ff' : '#ff9500',
|
||||
}}
|
||||
>
|
||||
{prov.configured ? '已配置' : '未配置'}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{apiProviders.length === 0 && (
|
||||
<p style={{ fontSize: 12, color: 'rgba(255,255,255,0.3)', margin: 0 }}>
|
||||
连接后端后显示 API 状态
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<p style={{ fontSize: 11, color: 'rgba(255,255,255,0.3)', margin: '10px 0 0 0' }}>
|
||||
API Key 需在后端环境变量中配置(如 ANTHROPIC_API_KEY)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Refresh Settings */}
|
||||
<div
|
||||
style={{
|
||||
|
||||
@@ -476,13 +476,20 @@ export function WorkflowPage() {
|
||||
|
||||
setUploading(true);
|
||||
try {
|
||||
await file.text();
|
||||
// 这里应该调用后端 API 上传文件
|
||||
// 暂时模拟成功
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
const res = await fetch('http://localhost:8000/api/workflows/upload', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({ detail: '上传失败' }));
|
||||
throw new Error(err.detail || '上传失败');
|
||||
}
|
||||
alert(`文件 ${file.name} 上传成功`);
|
||||
loadWorkflows();
|
||||
} catch (err) {
|
||||
alert('上传失败');
|
||||
alert(err instanceof Error ? err.message : '上传失败');
|
||||
} finally {
|
||||
setUploading(false);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user