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

@@ -494,6 +494,9 @@ export default function Home({ currentUser }: { currentUser: AuthUser }) {
const [activeNav, setActiveNav] = useState<NavKey>("overview");
const [loading, setLoading] = useState(true);
const [working, setWorking] = useState(false);
const [collectingDistributionIds, setCollectingDistributionIds] = useState<Set<string>>(
new Set(),
);
const [error, setError] = useState("");
const [toast, setToast] = useState("");
const [menuOpen, setMenuOpen] = useState(false);
@@ -762,10 +765,23 @@ export default function Home({ currentUser }: { currentUser: AuthUser }) {
};
const collectMetrics = async (distribution: Distribution) => {
await runAction(
{ action: "collect_now", distributionId: distribution.id },
"公开数据已更新",
);
setCollectingDistributionIds((current) => {
const next = new Set(current);
next.add(distribution.id);
return next;
});
try {
await runAction(
{ action: "collect_now", distributionId: distribution.id },
"公开数据已更新",
);
} finally {
setCollectingDistributionIds((current) => {
const next = new Set(current);
next.delete(distribution.id);
return next;
});
}
};
const updateDistributionPublishUrl = async (
@@ -1123,6 +1139,7 @@ export default function Home({ currentUser }: { currentUser: AuthUser }) {
tasks={data.tasks}
distributions={data.distributions}
working={working}
collectingDistributionIds={collectingDistributionIds}
canUpdatePublishUrl={isManager}
onCollect={collectMetrics}
onUpdatePublishUrl={updateDistributionPublishUrl}
@@ -2815,6 +2832,7 @@ function RecoveryPage({
tasks,
distributions,
working,
collectingDistributionIds,
canUpdatePublishUrl,
onCollect,
onUpdatePublishUrl,
@@ -2829,6 +2847,7 @@ function RecoveryPage({
tasks: Task[];
distributions: Distribution[];
working: boolean;
collectingDistributionIds: ReadonlySet<string>;
canUpdatePublishUrl: boolean;
onCollect: (distribution: Distribution) => void;
onUpdatePublishUrl: (distribution: Distribution) => void;
@@ -3116,7 +3135,9 @@ function RecoveryPage({
</span>
) : item.screenshot_key ? (
<span className="creator-ocr-failed">
KOC填写数据
{item.ocr_status === "processing"
? "正在识别数据"
: "识别失败,请手动填写"}
</span>
) : null}
{item.screenshot_key &&
@@ -3139,7 +3160,12 @@ function RecoveryPage({
) : (
<button
className="collect-button"
disabled={working || !noteUrl}
disabled={
!noteUrl ||
(working &&
(collectingDistributionIds.size === 0 ||
collectingDistributionIds.has(item.id)))
}
onClick={() => onCollect(item)}
>
{noteUrl ? "立即采集" : "待填链接"}

View File

@@ -4,9 +4,10 @@ import {
getUploadBucket,
} from "../../../lib/mvp-db";
import { adminForbidden, isAdminRequest } from "../../../lib/admin-auth";
import { verifyCreatorScreenshotAccessToken } from "../../../lib/creator-screenshot-access";
export async function GET(request: Request) {
if (!(await isAdminRequest(request))) return adminForbidden();
const isAdmin = await isAdminRequest(request);
try {
await ensureSchema();
const distributionId = new URL(request.url).searchParams
@@ -22,6 +23,18 @@ export async function GET(request: Request) {
)
.bind(distributionId)
.first<{ screenshot_key: string | null }>();
const mcpToken = new URL(request.url).searchParams.get("mcp_token") || "";
if (
!isAdmin &&
(!row?.screenshot_key ||
!verifyCreatorScreenshotAccessToken(
distributionId,
row.screenshot_key,
mcpToken,
))
) {
return adminForbidden();
}
if (
!row?.screenshot_key ||
!row.screenshot_key.startsWith("creator-center/")

View File

@@ -13,6 +13,13 @@ import {
parseResultScreenshotKeys,
serializeResultScreenshotKeys,
} from "../../../lib/result-screenshots";
import { creatorScreenshotMcpUrl } from "../../../lib/creator-screenshot-access";
import {
extractCreatorMetricsFromMcp,
resolveCollectionMcpConfig,
type CollectionMcpBindings,
} from "../../../lib/mcp-collection-client";
import { getRuntimeEnv } from "../../../lib/runtime-env";
async function readUpload(request: Request) {
const contentType = request.headers.get("content-type") ?? "";
@@ -163,13 +170,47 @@ async function handlePost(request: Request) {
screenshot_key = ?,
ocr_status = CASE
WHEN exposure IS NOT NULL AND views IS NOT NULL THEN ocr_status
ELSE 'uploaded'
ELSE 'processing'
END,
updated_at = CURRENT_TIMESTAMP
WHERE id = ?`,
)
.bind(key, upload.distributionId)
.run();
try {
const metrics = await extractCreatorMetricsFromMcp(
creatorScreenshotMcpUrl(request, upload.distributionId, key),
resolveCollectionMcpConfig(
getRuntimeEnv() as unknown as CollectionMcpBindings,
),
);
await getRawDb()
.prepare(
`UPDATE distributions SET exposure = ?, views = ?,
ocr_status = 'success', updated_at = CURRENT_TIMESTAMP
WHERE id = ?`,
)
.bind(metrics.exposure, metrics.views, upload.distributionId)
.run();
return Response.json({
uploaded: true,
exposure: metrics.exposure,
views: metrics.views,
ocrStatus: "success",
kind: "creator-center",
});
} catch (error) {
console.error("[KOC LOOP] creator screenshot OCR failed", {
distributionId: upload.distributionId,
detail: error instanceof Error ? error.message : String(error),
});
await getRawDb()
.prepare(
`UPDATE distributions SET ocr_status = 'failed', updated_at = CURRENT_TIMESTAMP WHERE id = ?`,
)
.bind(upload.distributionId)
.run();
}
} else {
await getRawDb()
.prepare(
@@ -189,6 +230,7 @@ async function handlePost(request: Request) {
: isCreatorCenter
? "creator-center"
: "publish",
ocrStatus: isCreatorCenter ? "failed" : undefined,
});
} catch (error) {
return Response.json(

View File

@@ -6,6 +6,13 @@ import {
uid,
} from "../../../lib/mvp-db";
import { adminForbidden, isAdminRequest } from "../../../lib/admin-auth";
import { creatorScreenshotMcpUrl } from "../../../lib/creator-screenshot-access";
import {
extractCreatorMetricsFromMcp,
resolveCollectionMcpConfig,
type CollectionMcpBindings,
} from "../../../lib/mcp-collection-client";
import { getRuntimeEnv } from "../../../lib/runtime-env";
export async function POST(request: Request) {
if (!(await isAdminRequest(request))) return adminForbidden();
@@ -33,12 +40,37 @@ export async function POST(request: Request) {
screenshot_key = ?,
exposure = NULL,
views = NULL,
ocr_status = 'failed',
ocr_status = 'processing',
updated_at = CURRENT_TIMESTAMP
WHERE id = ?`,
)
.bind(key, distributionId)
.run();
try {
const metrics = await extractCreatorMetricsFromMcp(
creatorScreenshotMcpUrl(request, distributionId, key),
resolveCollectionMcpConfig(getRuntimeEnv() as unknown as CollectionMcpBindings),
);
await db
.prepare(
`UPDATE distributions SET exposure = ?, views = ?,
ocr_status = 'success', updated_at = CURRENT_TIMESTAMP
WHERE id = ?`,
)
.bind(metrics.exposure, metrics.views, distributionId)
.run();
} catch (error) {
console.error("[KOC LOOP] creator screenshot OCR failed", {
distributionId,
detail: error instanceof Error ? error.message : String(error),
});
await db
.prepare(
`UPDATE distributions SET ocr_status = 'failed', updated_at = CURRENT_TIMESTAMP WHERE id = ?`,
)
.bind(distributionId)
.run();
}
return Response.json(await getDashboardData());
} catch (error) {
return Response.json(