feat: add MCP creator screenshot metrics recognition

This commit is contained in:
巫凤萍
2026-08-19 15:08:17 +08:00
parent 8f7ea0558d
commit ab3a544eec
12 changed files with 342 additions and 36 deletions

View File

@@ -954,7 +954,7 @@ export default function Home() {
selectedItem: Assignment,
file: File,
kind: "publish" | "creator-center" | "task-result",
) => {
): Promise<{ exposure?: number | null; views?: number | null; ocrStatus?: string }> => {
const compressed = await compressScreenshot(file);
const headers: Record<string, string> = {
"Content-Type": compressed.type || "image/jpeg",
@@ -973,7 +973,12 @@ export default function Home() {
headers,
body: compressed,
});
const uploadResult = (await uploadResponse.json()) as { error?: string };
const uploadResult = (await uploadResponse.json()) as {
error?: string;
exposure?: number | null;
views?: number | null;
ocrStatus?: string;
};
if (!uploadResponse.ok) {
throw new Error(
uploadResult.error ||
@@ -984,6 +989,7 @@ export default function Home() {
: "发布截图上传失败"),
);
}
return uploadResult;
};
const submitNote = async (event: FormEvent) => {
@@ -1082,22 +1088,31 @@ export default function Home() {
setToast("请选择创作者中心截图");
return;
}
if (
!/^\d{1,12}$/.test(creatorExposure) ||
!/^\d{1,12}$/.test(creatorViews)
) {
setToast("请填写正确的曝光量和阅读量");
return;
}
try {
setCreatorWorking(true);
let exposureValue = creatorExposure;
let viewsValue = creatorViews;
if (creatorScreenshot) {
setCreatorStage("正在上传截图…");
await uploadEvidence(
const ocrResult = await uploadEvidence(
selected,
creatorScreenshot,
"creator-center",
);
if (typeof ocrResult.exposure === "number") {
exposureValue = String(ocrResult.exposure);
setCreatorExposure(exposureValue);
}
if (typeof ocrResult.views === "number") {
viewsValue = String(ocrResult.views);
setCreatorViews(viewsValue);
}
if (ocrResult.ocrStatus === "success") {
setCreatorStage("已识别截图数据,正在保存…");
}
}
if (!/^\d{1,12}$/.test(exposureValue) || !/^\d{1,12}$/.test(viewsValue)) {
throw new Error("未识别到完整数据,请填写曝光量和阅读量");
}
setCreatorStage("正在保存数据…");
const response = await fetch(partnerApi("/api/partner"), {
@@ -1109,8 +1124,8 @@ export default function Home() {
claimToken,
delegationToken,
distributionId: selected.id,
exposure: creatorExposure,
views: creatorViews,
exposure: exposureValue,
views: viewsValue,
}),
});
const result = (await response.json()) as { error?: string };
@@ -1593,7 +1608,7 @@ export default function Home() {
<div className="evidence-empty-state">
<b></b>
<strong>7</strong>
<small>OCR</small>
<small></small>
</div>
)}
<label className="evidence-upload-action">

View File

@@ -92,7 +92,7 @@ test("keeps claiming minimal and backfill one-to-one", async () => {
assert.match(page, /submit_creator_metrics/);
assert.match(page, /creatorExposure/);
assert.match(page, /creatorViews/);
assert.match(page, /截图仅用于运营核对不再自动OCR/);
assert.match(page, /上传后自动识别曝光量和阅读量,识别失败可手动填写/);
assert.match(page, /evidenceImageUrl/);
assert.match(page, /params\.set\("v", evidenceKey\)/);
assert.match(page, /evidenceImageUrl\(selected, "publish"\)/);
@@ -140,7 +140,7 @@ test("renders screenshot-only tasks with anonymous delegation and multi-image up
assert.match(styles, /\.evidence-preview-button/);
});
test("shows D1 timestamps in Beijing time", () => {
test("shows D1 and MySQL UTC timestamps in Beijing time", () => {
const stored = "2026-07-29 05:36:00";
assert.equal(parseStoredDate(stored).toISOString(), "2026-07-29T05:36:00.000Z");
assert.match(formatShanghaiDate(stored, true), /07\/29.*13:36/);