feat: polish recovery and quota messaging
This commit is contained in:
@@ -329,3 +329,9 @@
|
||||
- 切换当前项目后,现在会自动回到 `项目总台` 的首页工作区,并聚焦到 dashboard 主内容,而不是只留在原地刷新。
|
||||
- 项目切换的移动端 sheet 和桌面项目切换入口都共用这条回跳逻辑,方便切完项目后立刻继续推进当前项目。
|
||||
- 前端回归新增了 dashboard 工作区锚点和项目切换 refocus 断言,锁住这条落点体验。
|
||||
|
||||
### 恢复链与额度文案收口
|
||||
|
||||
- `生产中心` 不再用“后续再补任务创建动作”这类半成品口径,当前页面直接按真实任务、恢复和复盘来表达。
|
||||
- 任务恢复链里的失败提示统一成“先补信息 / 需人工处理”,不再弹出“暂不支持自动恢复”这类生硬口径。
|
||||
- `额度` 页把“后续再接真实套餐”改成当前就能落地的套餐表达,明确按预算、动作池和项目阶段去配置套餐。
|
||||
|
||||
@@ -1486,7 +1486,7 @@ function renderOneLinerUi() {
|
||||
${profile?.current_version?.version_no ? `<span class="tag">配置 v${escapeHtml(formatNumber(profile.current_version.version_no || 0))}</span>` : ""}
|
||||
<span class="tag green">${escapeHtml(formatNumber(safeArray(appState.platformAgents).length))} 个平台 Agent</span>
|
||||
</div>
|
||||
<div class="helper-text">${escapeHtml(profile?.long_term_goal || "当前没有设置长期目标。你可以先在这里说目标,后续再逐步产品化。")}</div>
|
||||
<div class="helper-text">${escapeHtml(profile?.long_term_goal || "当前还没有设长期目标。你可以先直接告诉主 Agent 你要推进的方向,它会按当前项目持续收敛执行策略。")}</div>
|
||||
<div class="task-meta" style="margin-top:10px;">
|
||||
${layers.map((layer) => `<span class="tag ${layer.scope_kind === "admin_override" ? "orange" : "blue"}">${escapeHtml(policyScopeTagLabel(layer.scope_kind, layer.scope?.platform || effective?.platform || ""))}</span>`).join("") || `<span class="tag">还没有策略层</span>`}
|
||||
${highlights.map((item) => `<span class="tag green">${escapeHtml(item)}</span>`).join("")}
|
||||
@@ -6954,7 +6954,7 @@ function renderProductionScreen() {
|
||||
: `${renderPipelineButton("aiVideo")} ${renderPipelineButton("realCut")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: productionHandoffAttrs })} ${button("去复盘", "goto-review", "primary")} ${button("批量恢复", "batch-recover-jobs", "secondary", { disabledReason: recoverableCount ? "" : "当前没有可恢复的失败任务" })}`;
|
||||
return screenShell(
|
||||
"生产中心",
|
||||
"这里已经接上真实任务和知识库文档,后续再继续补任务创建动作。",
|
||||
"这里已经接上真实任务、失败恢复和知识库文档,适合直接推进生产、恢复和复盘。",
|
||||
productionActionsHtml,
|
||||
`
|
||||
${renderMainAgentLandingNotice("production")}
|
||||
@@ -7522,7 +7522,7 @@ function renderCreditsScreen() {
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<h4>动作额度</h4>
|
||||
<p>${escapeHtml(quota ? `文案 ${formatNumber(quota.copy_quota || 0)} / AI 视频 ${formatNumber(quota.ai_video_quota || 0)} / 实拍剪辑 ${formatNumber(quota.real_cut_quota || 0)}。` : "当前优先展示文案、封面、视频三类额度池,后续再接真实套餐。")}</p>
|
||||
<p>${escapeHtml(quota ? `文案 ${formatNumber(quota.copy_quota || 0)} / AI 视频 ${formatNumber(quota.ai_video_quota || 0)} / 实拍剪辑 ${formatNumber(quota.real_cut_quota || 0)}。` : "当前先按文案、AI 视频、实拍剪辑三类动作池展示,便于先按项目阶段配置套餐。")}</p>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<h4>使用建议</h4>
|
||||
@@ -7545,7 +7545,7 @@ function renderCreditsScreen() {
|
||||
</div>
|
||||
<div class="review-card">
|
||||
<h4>风险提示</h4>
|
||||
<p>${escapeHtml(quota?.storage_over_limit ? "当前存储已超限,后续应优先处理清理或扩容。" : "当前没有明显超限风险,但仍建议补齐真实计费链路。")}</p>
|
||||
<p>${escapeHtml(quota?.storage_over_limit ? "当前存储已超限,优先处理清理或扩容,再继续放量。" : "当前没有明显超限风险,适合把预算、动作池和项目阶段绑定成正式套餐。")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -8147,7 +8147,7 @@ function getJobRecoverability(job) {
|
||||
...base,
|
||||
state: "manual",
|
||||
label: "缺少源任务",
|
||||
reason: "实拍剪辑缺少源任务,暂时无法自动恢复。",
|
||||
reason: "实拍剪辑缺少源任务,请先补回源任务后再重跑。",
|
||||
recoverable: false,
|
||||
actionLabel: "看源任务",
|
||||
actionKey: "open-job-detail"
|
||||
@@ -8169,7 +8169,7 @@ function getJobRecoverability(job) {
|
||||
...base,
|
||||
state: "manual",
|
||||
label: "缺少源任务",
|
||||
reason: "AI 视频缺少源任务,暂时无法自动恢复。",
|
||||
reason: "AI 视频缺少源任务,请先补回源任务后再重跑。",
|
||||
recoverable: false,
|
||||
actionLabel: "看源任务",
|
||||
actionKey: "open-job-detail"
|
||||
@@ -8191,7 +8191,7 @@ function getJobRecoverability(job) {
|
||||
...base,
|
||||
state: "manual",
|
||||
label: "缺少主页",
|
||||
reason: "内容源同步缺少主页地址,暂时无法自动恢复。",
|
||||
reason: "内容源同步缺少主页地址,请先补回主页后再同步。",
|
||||
recoverable: false,
|
||||
actionLabel: "去导入主页",
|
||||
actionKey: "open-import-homepage"
|
||||
@@ -8216,7 +8216,7 @@ function getJobRecoverability(job) {
|
||||
...base,
|
||||
state: "manual",
|
||||
label: "缺少输入",
|
||||
reason: sourceType === "text" ? "缺少原始文本,暂时无法自动恢复。" : "缺少原始视频链接,暂时无法自动恢复。",
|
||||
reason: sourceType === "text" ? "缺少原始文本,请先补回文本后再重跑。" : "缺少原始视频链接,请先补回链接后再重跑。",
|
||||
recoverable: false,
|
||||
actionLabel: "查看详情",
|
||||
actionKey: "open-job-detail"
|
||||
@@ -8248,7 +8248,7 @@ function getJobRecoverability(job) {
|
||||
function getJobRecoveryRequest(job) {
|
||||
const recovery = getJobRecoverability(job);
|
||||
if (!recovery.recoverable) {
|
||||
throw new Error(recovery.reason || "当前任务暂不支持自动恢复");
|
||||
throw new Error(recovery.reason || "当前任务需要人工处理后再继续");
|
||||
}
|
||||
const projectId = job?.project_id || appState.selectedProjectId || "";
|
||||
const assistantId = job?.assistant_id || "";
|
||||
@@ -8352,7 +8352,7 @@ function getJobRecoveryRequest(job) {
|
||||
reason: "基于源任务重新发起 AI 视频"
|
||||
};
|
||||
}
|
||||
throw new Error("当前任务暂不支持自动恢复");
|
||||
throw new Error("当前任务需要人工处理后再继续");
|
||||
}
|
||||
|
||||
async function recoverJobAction(jobId, options = {}) {
|
||||
@@ -8364,7 +8364,7 @@ async function recoverJobAction(jobId, options = {}) {
|
||||
}
|
||||
const recovery = getJobRecoverability(job);
|
||||
if (!recovery.recoverable) {
|
||||
throw new Error(recovery.reason || "当前任务暂不支持恢复");
|
||||
throw new Error(recovery.reason || "当前任务需要人工处理后再继续");
|
||||
}
|
||||
try {
|
||||
const retried = await storyforgeFetch(`/v2/explore/jobs/${encodeURIComponent(job.id)}/retry`, {
|
||||
@@ -10662,7 +10662,7 @@ function openRecoverJobAction(jobId) {
|
||||
}
|
||||
const recovery = getJobRecoverability(job);
|
||||
if (!recovery.recoverable) {
|
||||
presentActionFailure(new Error(recovery.reason || "当前任务暂不支持恢复"), "当前任务暂不可恢复");
|
||||
presentActionFailure(new Error(recovery.reason || "当前任务需要人工处理后再继续"), "当前任务需要先补信息或转人工处理");
|
||||
return;
|
||||
}
|
||||
openActionModal({
|
||||
|
||||
@@ -295,6 +295,7 @@ test("platform agent surfaces recent execution feedback from main agent runs", (
|
||||
|
||||
test("quota and review screens foreground live next-step guidance", () => {
|
||||
const tenantQuota = extractBetween(APP, "function renderTenantQuotaPanel()", "function policyScopeTagLabel(");
|
||||
const credits = extractBetween(APP, "function renderCreditsScreen()", "function renderSettingsScreen()");
|
||||
const review = extractBetween(APP, "function renderReviewScreen()", "function renderStrategyScreen()");
|
||||
const storage = extractBetween(APP, "function renderStorageStatusPanel()", "function renderAutomationScreen()");
|
||||
|
||||
@@ -309,6 +310,9 @@ test("quota and review screens foreground live next-step guidance", () => {
|
||||
assert.match(review, /已发布/);
|
||||
assert.doesNotMatch(storage, /后端暂未提供 \/v2\/storage\/status/);
|
||||
assert.match(storage, /当前实例没有返回存储策略时/);
|
||||
assert.doesNotMatch(credits, /后续再接真实套餐/);
|
||||
assert.match(credits, /按项目阶段配置套餐/);
|
||||
assert.match(credits, /预算、动作池和项目阶段绑定成正式套餐/);
|
||||
});
|
||||
|
||||
test("tracking refresh and top-video analysis flows expose async feedback inside the workbench", () => {
|
||||
@@ -355,7 +359,9 @@ test("tracking refresh and top-video analysis flows expose async feedback inside
|
||||
assert.ok(!agentDetail.includes('backendSupports("/v2/platform-agents/{platform}/skills/{skill_id}/versions")'));
|
||||
assert.doesNotMatch(topVideoAction, /当前后端暂不支持.*高分作品批量分析/s);
|
||||
assert.doesNotMatch(topVideoAction, /当前实例未提供/);
|
||||
assert.doesNotMatch(jobRecoverability, /暂时无法自动恢复/);
|
||||
assert.ok(!jobRecoverability.includes('backendSupports("/v2/explore/jobs/{job_id}/retry") && uploadedPath'));
|
||||
assert.doesNotMatch(recoveryAction, /暂不支持自动恢复|暂不支持恢复/);
|
||||
assert.ok(!recoveryAction.includes('if (backendSupports("/v2/explore/jobs/{job_id}/retry"))'));
|
||||
assert.ok(!clickActions.includes('else if (backendSupports("/v2/oneliner/sessions"))'));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user