支持待确认事项逐条采纳
This commit is contained in:
@@ -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(["出海能力需要确认"]);
|
||||
});
|
||||
});
|
||||
@@ -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
@@ -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();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user