111 lines
3.3 KiB
TypeScript
111 lines
3.3 KiB
TypeScript
// AI Briefing 页面
|
|
|
|
import React, { useState, useEffect, useCallback } from 'react';
|
|
import * as api from '../api';
|
|
import type { Task, Project } from '../types';
|
|
|
|
export function Briefing() {
|
|
const [projects, setProjects] = useState<Project[]>([]);
|
|
const [selectedProject, setSelectedProject] = useState<string>('');
|
|
const [tasks, setTasks] = useState<Task[]>([]);
|
|
const [briefing, setBriefing] = useState<string>('');
|
|
|
|
const loadProjects = useCallback(async () => {
|
|
try {
|
|
const data = await api.listProjects();
|
|
setProjects(data);
|
|
if (data.length > 0 && !selectedProject) {
|
|
setSelectedProject(data[0].id);
|
|
}
|
|
} catch { /* ignore */ }
|
|
}, [selectedProject]);
|
|
|
|
useEffect(() => { loadProjects(); }, [loadProjects]);
|
|
|
|
const generateBriefing = useCallback(async () => {
|
|
if (!selectedProject) return;
|
|
try {
|
|
const data = await api.listTasks(selectedProject);
|
|
setTasks(data);
|
|
|
|
// 本地生成简要汇报
|
|
const done = data.filter(t => t.status === 'done');
|
|
const working = data.filter(t => t.status === 'working');
|
|
const failed = data.filter(t => t.status === 'failed');
|
|
const pending = data.filter(t => t.status === 'pending');
|
|
|
|
const lines = [
|
|
`📊 项目日报`,
|
|
``,
|
|
`总任务数: ${data.length}`,
|
|
`✅ 已完成: ${done.length}`,
|
|
`🔄 进行中: ${working.length}`,
|
|
`⏳ 待处理: ${pending.length}`,
|
|
`❌ 失败: ${failed.length}`,
|
|
``,
|
|
];
|
|
|
|
if (working.length > 0) {
|
|
lines.push(`## 进行中的任务`);
|
|
working.forEach(t => lines.push(`- [${t.id.substring(0, 8)}] ${t.title} (${t.assignee || '未分配'})`));
|
|
lines.push('');
|
|
}
|
|
|
|
if (failed.length > 0) {
|
|
lines.push(`## 失败的任务`);
|
|
failed.forEach(t => lines.push(`- [${t.id.substring(0, 8)}] ${t.title}`));
|
|
lines.push('');
|
|
}
|
|
|
|
if (done.length > 0) {
|
|
lines.push(`## 最近完成`);
|
|
done.slice(-5).forEach(t => lines.push(`- [${t.id.substring(0, 8)}] ${t.title}`));
|
|
}
|
|
|
|
setBriefing(lines.join('\n'));
|
|
} catch { /* ignore */ }
|
|
}, [selectedProject]);
|
|
|
|
return (
|
|
<div>
|
|
<h2 className="page-title">AI Briefing</h2>
|
|
|
|
<div style={{ display: 'flex', gap: 8, marginBottom: 16 }}>
|
|
<select
|
|
value={selectedProject}
|
|
onChange={e => setSelectedProject(e.target.value)}
|
|
style={{
|
|
padding: '6px 10px',
|
|
borderRadius: 'var(--radius)',
|
|
border: '1px solid var(--line)',
|
|
background: 'var(--bg)',
|
|
color: 'var(--fg)',
|
|
}}
|
|
>
|
|
<option value="">选择项目</option>
|
|
{projects.map(p => (
|
|
<option key={p.id} value={p.id}>{p.name}</option>
|
|
))}
|
|
</select>
|
|
<button className="btn btn-primary" onClick={generateBriefing}>
|
|
生成汇报
|
|
</button>
|
|
</div>
|
|
|
|
{briefing && (
|
|
<div className="card">
|
|
<pre style={{ whiteSpace: 'pre-wrap', fontFamily: 'var(--font)', lineHeight: 1.6 }}>
|
|
{briefing}
|
|
</pre>
|
|
</div>
|
|
)}
|
|
|
|
{!briefing && (
|
|
<div className="card">
|
|
<p style={{ color: 'var(--muted)' }}>选择项目并点击"生成汇报"查看项目状态概览。</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|