半自动+人工”链路已经落下第一版

This commit is contained in:
yml
2026-04-12 19:07:51 +08:00
parent 4c98b78267
commit 75647e9eed
24 changed files with 711 additions and 93 deletions
@@ -111,6 +111,7 @@ export async function createTencentBrowserSession(payload = {}) {
qrUpdatedAt: '',
lastState: null,
lastRedeem: null,
lastReview: null,
lastError: '',
presentationSyncAttempts: 0,
}
@@ -237,6 +238,11 @@ export async function refreshTencentBrowserSession(sessionId, { includeQrImage =
activityInfo,
loginType: session.loginType,
})
if (credentialReady && activityInfo?.role?.ready) {
await ensureReviewScreenshot(session, activityInfo)
}
session.updatedAt = new Date().toISOString()
session.expiresAt = Date.now() + SESSION_TTL_MS
@@ -335,6 +341,20 @@ export async function getTencentBrowserSessionScreenshotPath(sessionId) {
return screenshotPath
}
export async function getTencentBrowserSessionReviewScreenshotPath(sessionId) {
const session = await getRequiredSession(sessionId)
const screenshotPath = session.lastReview?.screenshotPath
if (!screenshotPath) {
throw new SessionServiceError('当前会话还没有客服复核截图', {
statusCode: 404,
errorCode: 'review_screenshot_not_ready',
})
}
return screenshotPath
}
async function ensureBrowser() {
if (!browserPromise) {
const launchOptions = resolveBrowserLaunchOptions()
@@ -910,6 +930,7 @@ function buildSessionPayload(session, { includeQrImage = true } = {}) {
qrUpdatedAt: session.qrUpdatedAt,
credentialReady: Boolean(session.lastState?.credentialReady),
activityInfo,
review: buildReviewPayload(session.lastReview),
redeem: buildRedeemPayload(session.lastRedeem),
artifacts: buildArtifactsPayload(session),
}
@@ -928,11 +949,34 @@ function buildSessionPayload(session, { includeQrImage = true } = {}) {
state: session.lastState,
browserDebug: resolveBrowserLaunchOptions(),
qrImagePath: session.qrImagePath,
review: buildReviewPayload(session.lastReview, { includeInternalPaths: true }),
redeem: buildRedeemPayload(session.lastRedeem, { includeInternalPaths: true }),
artifacts: buildArtifactsPayload(session, { includeInternalPaths: true }),
}
}
function buildReviewPayload(review, { includeInternalPaths = false } = {}) {
if (!review) {
return null
}
const payload = {
capturedAt: review.capturedAt,
roleId: review.roleId || '',
roleName: review.roleName || '',
}
if (!includeInternalPaths) {
return payload
}
return {
...payload,
screenshotPath: review.screenshotPath || '',
signature: review.signature || '',
}
}
function buildRedeemPayload(redeem, { includeInternalPaths = false } = {}) {
if (!redeem) {
return null
@@ -972,6 +1016,7 @@ function buildRedeemPayload(redeem, { includeInternalPaths = false } = {}) {
function buildArtifactsPayload(session, { includeInternalPaths = false } = {}) {
const payload = {
hasQrImage: Boolean(session.qrImageBase64),
hasReviewScreenshot: Boolean(session.lastReview?.screenshotPath),
hasScreenshot: Boolean(session.lastRedeem?.screenshotPath),
}
@@ -986,6 +1031,38 @@ function buildArtifactsPayload(session, { includeInternalPaths = false } = {}) {
screenshotPath: session.lastRedeem?.screenshotPath || '',
htmlPath: session.lastRedeem?.htmlPath || '',
resultPath: session.lastRedeem?.resultPath || '',
reviewScreenshotPath: session.lastReview?.screenshotPath || '',
}
}
async function ensureReviewScreenshot(session, activityInfo) {
const roleId = String(activityInfo?.role?.roleId || '').trim()
const roleName = String(activityInfo?.role?.roleName || '').trim()
const signature = `${session.loginType}|${roleId}|${roleName}`
if (session.lastReview?.screenshotPath && session.lastReview?.signature === signature) {
return
}
const screenshotPath = path.join(session.sessionDir, 'role-review.png')
try {
await session.page.screenshot({
path: screenshotPath,
fullPage: true,
})
session.lastReview = {
screenshotPath,
capturedAt: new Date().toISOString(),
roleId,
roleName,
signature,
}
} catch (error) {
logBrowserSessionDebug('reviewScreenshot.capture.error', {
sessionId: session.sessionId,
message: error instanceof Error ? error.message : String(error),
})
}
}