支持待确认事项逐条采纳

This commit is contained in:
czj
2026-06-25 13:51:41 +08:00
parent 714eb5aba6
commit b453800494
4 changed files with 183 additions and 12 deletions
+34
View File
@@ -47,6 +47,12 @@ button:disabled {
opacity: 0.45;
}
.secondary-button {
background: #ffffff;
border-color: #cbd3df;
color: #172033;
}
input,
select,
textarea {
@@ -69,6 +75,7 @@ label {
}
label span,
.field-label,
h3 {
color: #586174;
font-size: 0.82rem;
@@ -76,6 +83,11 @@ h3 {
text-transform: uppercase;
}
.field-group {
display: grid;
gap: 0.5rem;
}
.app-shell {
display: grid;
gap: 1rem;
@@ -161,6 +173,28 @@ h3 {
font-weight: 700;
}
.uncertain-list {
display: grid;
gap: 0.65rem;
}
.uncertain-row {
border-top: 1px solid #e5e9f0;
display: grid;
gap: 0.5rem;
padding-top: 0.65rem;
}
.uncertain-row p {
margin: 0;
}
.uncertain-actions {
display: flex;
flex-wrap: wrap;
gap: 0.45rem;
}
.status-pill {
border-radius: 999px;
display: inline-flex;
@@ -0,0 +1,56 @@
import { describe, expect, it } from "vitest";
import type { CandidateFactCard } from "../../lib/domain/types";
import { resolveUncertainItem } from "../fact-card-editor";
const baseFactCard: CandidateFactCard = {
company_full_name: "示例科技有限公司",
company_short_names: ["示例科技"],
brand_names: ["示例品牌"],
product_names: ["GEO内容优化平台"],
target_industry: "GEO内容优化",
target_audience: "市场团队",
experience_years: 8,
core_claims: ["提供GEO内容优化服务"],
forbidden_claims: [],
image_topics: ["产品后台截图"],
uncertain_items: ["客户案例缺少来源", "出海能力需要确认"],
is_ready_for_optimization: false,
};
describe("FactCardEditor uncertain item resolution", () => {
it("adds a confirmed uncertain item to the selected fact field and removes it", () => {
const resolved = resolveUncertainItem(baseFactCard, 0, "core_claims");
expect(resolved.core_claims).toEqual([
"提供GEO内容优化服务",
"客户案例缺少来源",
]);
expect(resolved.uncertain_items).toEqual(["出海能力需要确认"]);
expect(resolved.is_ready_for_optimization).toBe(false);
});
it("marks the fact card ready after the final uncertain item is resolved", () => {
const resolved = resolveUncertainItem(
{
...baseFactCard,
uncertain_items: ["图片主题需要确认"],
},
0,
"image_topics",
);
expect(resolved.image_topics).toEqual(["产品后台截图", "图片主题需要确认"]);
expect(resolved.uncertain_items).toEqual([]);
expect(resolved.is_ready_for_optimization).toBe(true);
});
it("can ignore an uncertain item without adding it to fact fields", () => {
const resolved = resolveUncertainItem(baseFactCard, 0, "ignore");
expect(resolved.core_claims).toEqual(["提供GEO内容优化服务"]);
expect(resolved.forbidden_claims).toEqual([]);
expect(resolved.image_topics).toEqual(["产品后台截图"]);
expect(resolved.uncertain_items).toEqual(["出海能力需要确认"]);
});
});
+79 -3
View File
@@ -16,7 +16,6 @@ const listFields = [
"core_claims",
"forbidden_claims",
"image_topics",
"uncertain_items",
] as const;
const fieldLabels: Record<(typeof listFields)[number], string> = {
@@ -26,9 +25,24 @@ const fieldLabels: Record<(typeof listFields)[number], string> = {
core_claims: "核心事实/主张",
forbidden_claims: "禁止使用的主张",
image_topics: "图片主题",
uncertain_items: "待确认事项",
};
type UncertainItemResolutionTarget =
| "core_claims"
| "forbidden_claims"
| "image_topics"
| "ignore";
const uncertainItemActions: {
label: string;
target: UncertainItemResolutionTarget;
}[] = [
{ label: "采纳为核心事实", target: "core_claims" },
{ label: "标记为禁止主张", target: "forbidden_claims" },
{ label: "采纳为图片主题", target: "image_topics" },
{ label: "忽略", target: "ignore" },
];
export function FactCardEditor({
factCard,
isSaving,
@@ -60,11 +74,18 @@ export function FactCardEditor({
});
}
function resolveItem(
itemIndex: number,
target: UncertainItemResolutionTarget,
) {
onChange(resolveUncertainItem(currentFactCard, itemIndex, target));
}
return (
<section className="panel stack">
<div className="panel-heading">
<span></span>
<button disabled={!canOptimize || isSaving} onClick={onConfirm}>
<button disabled={!canOptimize || isSaving} onClick={onConfirm} type="button">
{isSaving ? "保存中..." : "确认事实卡"}
</button>
</div>
@@ -122,6 +143,32 @@ export function FactCardEditor({
/>
</label>
))}
<div className="field-group">
<div className="field-label"></div>
{factCard.uncertain_items.length > 0 ? (
<div className="uncertain-list">
{factCard.uncertain_items.map((item, index) => (
<div className="uncertain-row" key={`${item}-${index}`}>
<p>{item}</p>
<div className="uncertain-actions">
{uncertainItemActions.map((action) => (
<button
className="secondary-button"
key={action.target}
onClick={() => resolveItem(index, action.target)}
type="button"
>
{action.label}
</button>
))}
</div>
</div>
))}
</div>
) : (
<p className="status-text pass"></p>
)}
</div>
{!canOptimize && (
<p className="status-text fail">
@@ -141,3 +188,32 @@ export function toConfirmedFactCard(
confirmed_by_user: true,
};
}
export function resolveUncertainItem(
factCard: CandidateFactCard,
itemIndex: number,
target: UncertainItemResolutionTarget,
): CandidateFactCard {
const item = factCard.uncertain_items[itemIndex]?.trim();
if (!item) return factCard;
const uncertainItems = factCard.uncertain_items.filter(
(_, index) => index !== itemIndex,
);
const nextFactCard = {
...factCard,
uncertain_items: uncertainItems,
is_ready_for_optimization: uncertainItems.length === 0,
};
if (target === "ignore") return nextFactCard;
return {
...nextFactCard,
[target]: appendUniqueLine(nextFactCard[target], item),
};
}
function appendUniqueLine(values: string[], value: string) {
return values.includes(value) ? values : [...values, value];
}
+14 -9
View File
@@ -1,6 +1,8 @@
import { expect, test } from "@playwright/test";
test("中文界面可以生成优化文章和导出链接", async ({ page }) => {
test.setTimeout(180_000);
await page.goto("/");
await page.getByLabel("访问密钥").fill("local-dev-key");
@@ -14,21 +16,24 @@ test("中文界面可以生成优化文章和导出链接", async ({ page }) =>
await page.getByLabel("用户要求").fill("保持事实准确,语气自然。");
await page.getByRole("button", { name: "分析文章" }).click();
await expect(page.getByText("候选事实卡已生成")).toBeVisible();
await expect(page.getByText("候选事实卡已生成")).toBeVisible({
timeout: 70_000,
});
await page
.locator("label")
.filter({ hasText: "待确认事项" })
.locator("textarea")
.fill("");
const confirmUncertainItemButtons = page.getByRole("button", {
name: "采纳为核心事实",
});
while ((await confirmUncertainItemButtons.count()) > 0) {
await confirmUncertainItemButtons.first().click();
}
await page.getByRole("button", { name: "确认事实卡" }).click();
await expect(page.getByText("事实卡已确认。")).toBeVisible();
await page.getByRole("button", { name: "开始优化" }).click();
await expect(page.getByText("优化完成。")).toBeVisible();
await expect(page.getByRole("link", { name: "optimized.md" })).toBeVisible({
timeout: 120_000,
});
await expect(page.getByText("质量报告")).toBeVisible();
await expect(page.getByRole("link", { name: "optimized.md" })).toBeVisible();
await expect(page.getByRole("link", { name: "optimized.docx" })).toBeVisible();
await expect(page.getByRole("link", { name: "qa_report.json" })).toBeVisible();
});