feat: 前后端+飞书全链路联调成功 - 创建项目自动发送飞书通知
This commit is contained in:
13
package-lock.json
generated
13
package-lock.json
generated
@@ -9,6 +9,7 @@
|
|||||||
"version": "0.5.0",
|
"version": "0.5.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@arco-design/web-react": "^2.66.0",
|
"@arco-design/web-react": "^2.66.0",
|
||||||
|
"@hono/node-server": "^1.19.13",
|
||||||
"@larksuiteoapi/node-sdk": "^1.60.0",
|
"@larksuiteoapi/node-sdk": "^1.60.0",
|
||||||
"hono": "^4.7.0",
|
"hono": "^4.7.0",
|
||||||
"react": "^18.3.0",
|
"react": "^18.3.0",
|
||||||
@@ -1020,6 +1021,18 @@
|
|||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@hono/node-server": {
|
||||||
|
"version": "1.19.13",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@hono/node-server/-/node-server-1.19.13.tgz",
|
||||||
|
"integrity": "sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.14.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"hono": "^4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@humanfs/core": {
|
"node_modules/@humanfs/core": {
|
||||||
"version": "0.19.1",
|
"version": "0.19.1",
|
||||||
"resolved": "https://registry.npmmirror.com/@humanfs/core/-/core-0.19.1.tgz",
|
"resolved": "https://registry.npmmirror.com/@humanfs/core/-/core-0.19.1.tgz",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@arco-design/web-react": "^2.66.0",
|
"@arco-design/web-react": "^2.66.0",
|
||||||
|
"@hono/node-server": "^1.19.13",
|
||||||
"@larksuiteoapi/node-sdk": "^1.60.0",
|
"@larksuiteoapi/node-sdk": "^1.60.0",
|
||||||
"hono": "^4.7.0",
|
"hono": "^4.7.0",
|
||||||
"react": "^18.3.0",
|
"react": "^18.3.0",
|
||||||
|
|||||||
@@ -185,13 +185,29 @@ const WizardPage: React.FC = () => {
|
|||||||
const [project, setProject] = useState<ProjectData>(createEmptyProject());
|
const [project, setProject] = useState<ProjectData>(createEmptyProject());
|
||||||
const [submitted, setSubmitted] = useState(false);
|
const [submitted, setSubmitted] = useState(false);
|
||||||
|
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
const next = () => setCurrent(Math.min(current + 1, STEPS.length - 1));
|
const next = () => setCurrent(Math.min(current + 1, STEPS.length - 1));
|
||||||
const prev = () => setCurrent(Math.max(current - 1, 0));
|
const prev = () => setCurrent(Math.max(current - 1, 0));
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = async () => {
|
||||||
|
setSubmitting(true);
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/projects', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(project),
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
setSubmitted(true);
|
setSubmitted(true);
|
||||||
Message.success(`项目「${project.name}」已创建!`);
|
Message.success(`项目「${project.name}」已创建!飞书通知已发送。`);
|
||||||
console.log('Project created:', project);
|
console.log('Project created:', data);
|
||||||
|
} catch (err) {
|
||||||
|
Message.error('创建失败,请重试');
|
||||||
|
console.error('Failed to create project:', err);
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderStep = () => {
|
const renderStep = () => {
|
||||||
@@ -328,6 +344,7 @@ const WizardPage: React.FC = () => {
|
|||||||
type="primary"
|
type="primary"
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={!project.name || !project.goal}
|
disabled={!project.name || !project.goal}
|
||||||
|
loading={submitting}
|
||||||
>
|
>
|
||||||
创建项目
|
创建项目
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
72
src/server/dev.ts
Normal file
72
src/server/dev.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
// Dev server entry - runs Hono on Node.js
|
||||||
|
import { Hono } from 'hono';
|
||||||
|
import { cors } from 'hono/cors';
|
||||||
|
import { logger } from 'hono/logger';
|
||||||
|
import { executionApiHandlers } from './execution-api';
|
||||||
|
import { handleDecompose, handleFeishuCallback } from './index';
|
||||||
|
import { sendFeishuMessage, notifyProjectCreated, notifyMilestoneReminder, notifyRiskAlert, sendDecisionCard } from './feishu';
|
||||||
|
import { handleDecisionCallback, createDecision, getPendingDecisions, DECISION_TEMPLATES } from '../lib/decision-cards';
|
||||||
|
|
||||||
|
const projects: Record<string, any> = {};
|
||||||
|
|
||||||
|
const app = new Hono();
|
||||||
|
|
||||||
|
app.use('*', cors());
|
||||||
|
app.use('*', logger());
|
||||||
|
|
||||||
|
app.get('/api/health', (c) => c.json({ status: 'ok', version: '0.5.0', message: 'FlowPilot API is running' }));
|
||||||
|
|
||||||
|
// Create project + send Feishu notification
|
||||||
|
app.post('/api/projects', async (c) => {
|
||||||
|
const body = await c.req.json();
|
||||||
|
const projectId = `proj-${Date.now()}`;
|
||||||
|
const project = { id: projectId, ...body, status: 'active', createdAt: new Date().toISOString() };
|
||||||
|
projects[projectId] = project;
|
||||||
|
|
||||||
|
// Send Feishu notification
|
||||||
|
try {
|
||||||
|
await notifyProjectCreated(project.name || '未命名项目', project.goal || '', 'ou_41d14aca8278e605d98e33b1221777e4', 'open_id');
|
||||||
|
console.log('✅ Feishu notification sent for project:', project.name);
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error('❌ Feishu notification failed:', e.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.json(project);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/api/projects/:id', (c) => c.json(projects[c.req.param('id')] || { error: 'not found' }));
|
||||||
|
app.get('/api/projects/:id/tasks', (c) => c.json({ tasks: [], projectId: c.req.param('id') }));
|
||||||
|
|
||||||
|
app.post('/api/projects/:id/tasks', async (c) => {
|
||||||
|
const body = await c.req.json();
|
||||||
|
return c.json({ id: `task-${Date.now()}`, projectId: c.req.param('id'), ...body, status: 'todo', createdAt: new Date().toISOString() });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.patch('/api/projects/:id/tasks/:taskId', async (c) => {
|
||||||
|
const body = await c.req.json();
|
||||||
|
return c.json({ id: c.req.param('taskId'), ...body, updatedAt: new Date().toISOString() });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/api/projects/:id/executions', (c) => c.json({ executions: executionApiHandlers.getExecutions(c.req.param('id')) }));
|
||||||
|
app.post('/api/projects/:id/executions', async (c) => { const b = await c.req.json(); return c.json(executionApiHandlers.createExecution(c.req.param('id'), b), 201); });
|
||||||
|
app.get('/api/projects/:id/stats', (c) => c.json(executionApiHandlers.getStats(c.req.param('id'))));
|
||||||
|
|
||||||
|
app.post('/api/projects/:id/decompose', async (c) => {
|
||||||
|
const b = await c.req.json();
|
||||||
|
return c.json(handleDecompose(b.task, b.context));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/api/projects/:id/decisions', (c) => c.json({ decisions: executionApiHandlers.getDecisions(c.req.param('id')) }));
|
||||||
|
app.post('/api/projects/:id/decisions', async (c) => { const b = await c.req.json(); return c.json(executionApiHandlers.createDecision(c.req.param('id'), b), 201); });
|
||||||
|
|
||||||
|
app.post('/api/feishu/webhook', async (c) => { const e = await c.req.json(); return c.json(handleFeishuCallback(e)); });
|
||||||
|
app.post('/api/feishu/decision/callback', async (c) => { const b = await c.req.json(); console.log('Decision callback:', JSON.stringify(b)); return c.json({ ok: true }); });
|
||||||
|
|
||||||
|
// Start with Node.js native http
|
||||||
|
const port = Number(process.env.PORT) || 3001;
|
||||||
|
import { createServer } from 'http';
|
||||||
|
import { serve } from '@hono/node-server';
|
||||||
|
|
||||||
|
serve({ fetch: app.fetch, port }, () => {
|
||||||
|
console.log(`🚀 FlowPilot API running on http://localhost:${port}`);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user