Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions docs/engineering-optimization-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# FlintStudio 工程优化方案(代码质量 + 产品策划)

## 目标

在不改变核心业务流程(`analyze_novel -> ... -> video`)的前提下,优先提升:

1. **可维护性**:减少 lint 噪音,让“真正的问题”更显眼。
2. **可观测性**:增强阶段视图与当前流程状态的一致性,降低排障成本。
3. **产品体验一致性**:让关键数据卡片、异常状态、交互行为具备明确语义。

---

## 多维度优化建议(策划层)

### 1) 研发效率与代码质量

- 建议把 lint warning 逐步收敛到 0,并在 CI 中使用 `--max-warnings=0` 阶段性门禁。
- 对 `src/lib/workflow`、`src/app/api/workflows` 增补轻量级契约测试(尤其是 phase 状态转移)。
- 统一“前端展示状态”与“后端步骤状态”枚举来源,避免并行维护。

### 2) 工作流产品能力

- 在 DAG 节点详情中加入“重试次数 / 最近错误摘要 / 上游依赖耗时”。
- 提供“失败后推荐动作”模板(重新分场、降低出图并发、切换模型)。
- 项目统计卡片可加入“近 24 小时产能”和“平均每集耗时”作为运营指标。

### 3) 运行与稳定性

- BullMQ 任务建议补充队列级别的积压阈值告警。
- 对图像/语音供应商 API 增加熔断事件审计,形成故障画像。
- 在 `/api/health` 增加数据库延迟与 Redis ping 延迟字段,便于 SRE 快速判断瓶颈层。

---

## 本次已执行的改进(可立即落地)

1. 清理了前端表单中的无用异常变量,减少 lint warning 干扰。
2. 修复统计卡片中图标命名与 JSX a11y 规则冲突的问题。
3. DAG 视图增加 `currentPhase` 驱动的默认选中逻辑,页面初始即可定位当前阶段详情。
4. 移除未使用的 `setEdges` 变量,收敛无效状态引用。

> 以上改动均为低风险、可回滚、小步提交,适合作为“质量基线收敛”的第一轮。

## 第二轮功能优化(本次新增)

1. **DAG 进度可视化增强**:新增总进度条(完成数/总数 + 百分比)和运行中/失败计数。
2. **当前阶段显式提示**:在图上方展示后端 `currentPhase` 对应中文阶段,避免“图上看不出正在跑哪一步”。
3. **项目统计扩展**:新增“场次数”指标卡,补齐从集数→场次→分镜→配音→总耗时的核心链路数据。
2 changes: 1 addition & 1 deletion src/app/[locale]/settings/prompt-config-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export function PromptConfigForm() {
if (!res.ok) throw new Error("保存失败");
setMessage({ type: "success", text: "提示词已保存" });
setTimeout(() => setMessage(null), 3000);
} catch (e) {
} catch {
setMessage({ type: "error", text: "保存失败,请重试" });
} finally {
setSaving(false);
Expand Down
12 changes: 9 additions & 3 deletions src/app/[locale]/workspace/[projectId]/project-stats.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { useState, useEffect } from "react";
import { Film, Image, Mic, Clock } from "lucide-react";
import { Film, Image as ImageIcon, Mic, Clock, Clapperboard } from "lucide-react";

type Stats = {
episodeCount: number;
Expand Down Expand Up @@ -51,13 +51,19 @@ export function ProjectStats({ projectId }: { projectId: string }) {
sub: stats.completedEpisodes > 0 ? "已完成" : "已生成",
},
{
icon: <Image className="h-4 w-4" />,
icon: <ImageIcon className="h-4 w-4" />,
label: "分镜图",
value: stats.imagesGenerated > 0
? `${stats.imagesGenerated} / ${stats.panelCount}`
: String(stats.panelCount),
sub: stats.imagesGenerated > 0 ? "已出图" : "分镜数",
},
{
icon: <Clapperboard className="h-4 w-4" />,
label: "场次数",
value: String(stats.clipCount),
sub: "总场次",
},
{
icon: <Mic className="h-4 w-4" />,
label: "配音行",
Expand All @@ -76,7 +82,7 @@ export function ProjectStats({ projectId }: { projectId: string }) {

return (
<section className="mt-6 animate-fade-in">
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3">
{cards.map((c) => (
<div
key={c.label}
Expand Down
50 changes: 46 additions & 4 deletions src/app/[locale]/workspace/[projectId]/workflow-pipeline-dag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,18 @@ export function PipelineDagView({
);

const [nodesState, setNodes, onNodesChange] = useNodesState(nodes);
const [edgesState, setEdges, onEdgesChange] = useEdgesState(edges);
const [edgesState, , onEdgesChange] = useEdgesState(edges);

useEffect(() => {
setNodes(nodes);
}, [nodes, setNodes]);

useEffect(() => {
if (!currentPhase) return;
const phaseExists = PIPELINE_PHASES.some((phase) => phase.key === currentPhase);
if (phaseExists) setSelectedPhaseKey(currentPhase);
}, [currentPhase]);

const onNodeClick: NodeMouseHandler = useCallback(
(_, node) => {
const phaseKey = node.data?.phaseKey;
Expand All @@ -181,12 +187,48 @@ export function PipelineDagView({
const selectedLabel = selectedPhaseKey
? PIPELINE_PHASES.find((p) => p.key === selectedPhaseKey)?.label ?? selectedPhaseKey
: null;
const currentPhaseLabel = currentPhase
? PIPELINE_PHASES.find((p) => p.key === currentPhase)?.label ?? currentPhase
: null;

const phaseMetrics = useMemo(() => {
const statusList = nodesState.map((node) => node.data.status);
const completed = statusList.filter((status) => status === "completed").length;
const running = statusList.filter((status) => status === "running").length;
const failed = statusList.filter((status) => status === "failed").length;
const total = statusList.length;
const progress = total > 0 ? Math.round((completed / total) * 100) : 0;
return { completed, running, failed, total, progress };
}, [nodesState]);

return (
<div className="mt-4 rounded-xl border border-[var(--border)] bg-[var(--background)]/60 overflow-hidden">
<p className="px-4 pt-3 text-xs font-medium uppercase tracking-wider text-[var(--muted)]">
流水线 DAG · 点击节点查看阶段详情(输入/输出/用时)
</p>
<div className="px-4 pt-3">
<div className="flex flex-wrap items-center gap-2 text-xs font-medium uppercase tracking-wider text-[var(--muted)]">
<span>流水线 DAG · 点击节点查看阶段详情(输入/输出/用时)</span>
{currentPhaseLabel && (
<span className="rounded-full border border-[var(--accent)]/40 bg-[var(--accent)]/10 px-2 py-0.5 normal-case text-[var(--accent)]">
当前阶段:{currentPhaseLabel}
</span>
)}
</div>
<div className="mt-2">
<div className="mb-1 flex items-center justify-between text-[11px] text-[var(--muted)]">
<span>进度 {phaseMetrics.completed}/{phaseMetrics.total}</span>
<span>{phaseMetrics.progress}%</span>
</div>
<div className="h-1.5 w-full overflow-hidden rounded-full bg-[var(--muted)]/20">
<div
className="h-full rounded-full bg-[var(--accent)] transition-all"
style={{ width: `${phaseMetrics.progress}%` }}
/>
</div>
<div className="mt-1 flex gap-3 text-[11px] text-[var(--muted)]">
<span>运行中 {phaseMetrics.running}</span>
<span>失败 {phaseMetrics.failed}</span>
</div>
</div>
</div>
<div className="h-[200px] w-full">
<ReactFlow
nodes={nodesState}
Expand Down