feat(desktop): add Wenxin monitoring adapter and surface platform risk control
Introduces the Wenxin (文心一言) monitoring adapter and registers it in the runtime controller and adapter index. Adds a new risk_control failure classification with custom classifiers for Doubao and Wenxin that recognize rate-limit, 频控 and 访问环境异常 signals, and propagates this state through account-health, runtime activity alerts, and the renderer views so users see "触发风控" instead of a generic challenge message on the Home, Accounts, and AI Platforms screens. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,7 @@ export interface RuntimeAccount {
|
||||
| "login_redirect"
|
||||
| "http_401"
|
||||
| "captcha_gate"
|
||||
| "risk_control"
|
||||
| "missing_partition"
|
||||
| "silent_refresh_failed"
|
||||
| "manual_unbind"
|
||||
|
||||
@@ -90,7 +90,7 @@ function authStateLabel(account: AccountRow): string {
|
||||
case "expired":
|
||||
return "授权过期";
|
||||
case "attention":
|
||||
return "需人工验证";
|
||||
return account.authReason === "risk_control" ? "触发风控" : "需人工验证";
|
||||
default:
|
||||
return account.probeState === "network_error" ? "最近校验失败" : "待重新校验";
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ function authLabel(account: AccountRow | null) {
|
||||
case "active":
|
||||
return "授权正常";
|
||||
case "challenge_required":
|
||||
return "需人工验证";
|
||||
return account.authReason === "risk_control" ? "触发风控" : "需人工验证";
|
||||
case "expired":
|
||||
return "授权过期";
|
||||
case "expiring_soon":
|
||||
@@ -88,6 +88,22 @@ function authColor(account: AccountRow | null) {
|
||||
}
|
||||
}
|
||||
|
||||
function accountActionAlert(account: AccountRow | null): string | null {
|
||||
if (!account || account.authState !== "challenge_required") {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (account.authReason === "risk_control" && account.platform === "doubao") {
|
||||
return "豆包账号疑似触发风控,监控已暂停。请点击“打开平台”,在前台完成验证或等待限制解除后,再刷新状态。";
|
||||
}
|
||||
|
||||
if (account.authReason === "risk_control") {
|
||||
return "当前账号疑似触发平台限制,相关任务已暂停。请先打开平台处理验证后,再刷新状态。";
|
||||
}
|
||||
|
||||
return "当前账号需要人工验证,相关任务会暂停执行。请先打开平台完成验证后,再刷新状态。";
|
||||
}
|
||||
|
||||
function verificationLabel(account: AccountRow): string {
|
||||
if (account.lastVerifiedAt) {
|
||||
return formatVerifiedAtLabel(account.lastVerifiedAt);
|
||||
@@ -328,6 +344,19 @@ async function unbindPlatform(account: AccountRow) {
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<div v-if="accountActionAlert(platform.account)" class="card-alert">
|
||||
<a-alert
|
||||
type="warning"
|
||||
show-icon
|
||||
banner
|
||||
>
|
||||
<template #message>
|
||||
{{ accountActionAlert(platform.account) }}
|
||||
</template>
|
||||
<template #icon><WarningOutlined /></template>
|
||||
</a-alert>
|
||||
</div>
|
||||
|
||||
<div v-if="platform.duplicateCount > 0" class="card-alert">
|
||||
<a-alert
|
||||
type="warning"
|
||||
|
||||
@@ -21,8 +21,10 @@ function translatePlatform(platform: string) {
|
||||
return map[platform?.toLowerCase()] || titleCaseToken(platform);
|
||||
}
|
||||
|
||||
function expiredAccountLabel(authState: string) {
|
||||
switch (authState) {
|
||||
function blockedAccountLabel(account: { authState: string; authReason: string | null }) {
|
||||
switch (account.authState) {
|
||||
case "challenge_required":
|
||||
return account.authReason === "risk_control" ? "触发风控" : "待验证";
|
||||
case "revoked":
|
||||
return "已停用";
|
||||
case "expired":
|
||||
@@ -83,8 +85,8 @@ const tasks = computed(() => snapshot.value?.tasks ?? []);
|
||||
const accounts = computed(() => snapshot.value?.accounts ?? []);
|
||||
const activity = computed(() => snapshot.value?.activity ?? []);
|
||||
|
||||
const expiredAccounts = computed(() =>
|
||||
accounts.value.filter((item) => ["expired", "revoked"].includes(item.authState)),
|
||||
const blockedAccounts = computed(() =>
|
||||
accounts.value.filter((item) => ["expired", "revoked", "challenge_required"].includes(item.authState)),
|
||||
);
|
||||
|
||||
const subsystemCards = computed(() => {
|
||||
@@ -159,23 +161,23 @@ const subsystemCards = computed(() => {
|
||||
<div class="card-header">
|
||||
<div class="header-text">
|
||||
<h3>账号健康看板</h3>
|
||||
<p>这里只展示已经确认过期、需要重新授权的账号。</p>
|
||||
<p>这里只展示已经确认过期、被风控或需要人工处理的账号。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="expiredAccounts.length > 0" class="info-list">
|
||||
<div v-for="account in expiredAccounts" :key="account.id" class="info-row record-row">
|
||||
<div v-if="blockedAccounts.length > 0" class="info-list">
|
||||
<div v-for="account in blockedAccounts" :key="account.id" class="info-row record-row">
|
||||
<div class="info-stack">
|
||||
<span class="title">{{ account.displayName }}</span>
|
||||
<span class="subtitle">{{ translatePlatform(account.platform) }} · <span class="mono-text">{{ formatUid(account.platformUid) }}</span></span>
|
||||
</div>
|
||||
<div class="info-stack" style="align-items: flex-end;">
|
||||
<StatusBadge :tone="healthTone(account.health)" :label="expiredAccountLabel(account.authState)" />
|
||||
<StatusBadge :tone="healthTone(account.health)" :label="blockedAccountLabel(account)" />
|
||||
<span class="subtitle" style="margin-top: 6px;">{{ formatRelativeTime(account.lastVerifiedAt ?? account.lastSyncAt) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="empty-state">
|
||||
<p>当前没有已过期账号。</p>
|
||||
<p>当前没有需要人工处理的阻断账号。</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user