chore: polish remaining workbench copy
Some checks failed
StoryForge CI / Baseline checks (push) Has been cancelled
StoryForge CI / Backend tests (push) Has been cancelled
StoryForge CI / Web tests (push) Has been cancelled

This commit is contained in:
kris
2026-04-05 03:30:13 +08:00
parent 5cff65417f
commit cd4021cf17
3 changed files with 38 additions and 22 deletions

View File

@@ -341,3 +341,15 @@
- `额度` 页和租户额度编辑弹层新增了 `套餐档位``预算预警阈值`,现在能直接按试用、增长、规模、自定义四档去配置项目套餐。 - `额度` 页和租户额度编辑弹层新增了 `套餐档位``预算预警阈值`,现在能直接按试用、增长、规模、自定义四档去配置项目套餐。
- 租户额度面板会直接展示当前套餐档位和预警阈值,便于把预算和动作池表达成正式产品能力,而不是只看裸配额数字。 - 租户额度面板会直接展示当前套餐档位和预警阈值,便于把预算和动作池表达成正式产品能力,而不是只看裸配额数字。
- 不可自动恢复的失败任务现在会打开站内“处理建议”面板,直接给出补信息、查看详情或交给主 Agent 的下一步,而不是只停在失败提示。 - 不可自动恢复的失败任务现在会打开站内“处理建议”面板,直接给出补信息、查看详情或交给主 Agent 的下一步,而不是只停在失败提示。
### 项目切换入口统一
- 所有 `select-project` 入口现在都统一走 `applySelectedProject()`,不再一部分入口回到项目总台、一部分入口只原地刷新。
- 项目卡、项目 sheet 和其他项目切换入口都会在切换后回到 `项目总台` 主工作区,保证切完项目就能直接继续当前项目推进。
### 页面口径继续去掉半成品表达
- `Agent`、模型设置、跟踪、对标关系、复盘这些页面里的“后续再补”口径继续改成当前就能执行的表达,页面语气更像正式产品。
- `创建 Agent / 编辑 Agent` 里的系统提示词占位改成“可先留空,后面随时补充”,减少半成品感。
- `作品与成片`、Agent 执行项默认说明里的“再补”字眼也一起收掉,统一成当前可直接推进的表达。
- 前端回归新增了这批文案断言,避免旧的占位口径再回流到主工作台。

View File

@@ -4547,7 +4547,7 @@ function renderPlatformAgentPanel() {
return ` return `
<div class="entity-card pad"> <div class="entity-card pad">
<div class="cell-title">${escapeHtml(item.name || item.platform_label)}</div> <div class="cell-title">${escapeHtml(item.name || item.platform_label)}</div>
<div class="cell-desc">${escapeHtml(item.mission || item.notes || "先绑定执行 Agent任务目标和方法论。")}</div> <div class="cell-desc">${escapeHtml(item.mission || item.notes || "先绑定执行 Agent完善任务目标和方法论。")}</div>
<div class="entity-meta"> <div class="entity-meta">
<span class="tag ${item.status === "active" ? "green" : "blue"}">${escapeHtml(item.status || "draft")}</span> <span class="tag ${item.status === "active" ? "green" : "blue"}">${escapeHtml(item.status || "draft")}</span>
${item.readiness_label ? `<span class="tag ${item.readiness_score >= 75 ? "green" : item.readiness_score >= 50 ? "blue" : "orange"}">${escapeHtml(item.readiness_label)} ${escapeHtml(formatNumber(item.readiness_score || 0))}</span>` : ""} ${item.readiness_label ? `<span class="tag ${item.readiness_score >= 75 ? "green" : item.readiness_score >= 50 ? "blue" : "orange"}">${escapeHtml(item.readiness_label)} ${escapeHtml(formatNumber(item.readiness_score || 0))}</span>` : ""}
@@ -6685,7 +6685,7 @@ function renderPlaybookScreen() {
<div class="panel-head"> <div class="panel-head">
<div> <div>
<h3>当前 Agent</h3> <h3>当前 Agent</h3>
<div class="panel-subtitle">后续文案生成、对标绑定和复盘默认都会优先使用这里选中的 Agent。</div> <div class="panel-subtitle">文案生成、对标绑定和复盘都会优先使用这里选中的 Agent。</div>
</div> </div>
<div class="task-meta"> <div class="task-meta">
${currentAssistant ? `<span class="tag blue">已选</span>` : `<span class="tag red">未选</span>`} ${currentAssistant ? `<span class="tag blue">已选</span>` : `<span class="tag red">未选</span>`}
@@ -7089,7 +7089,7 @@ function renderProductionScreen() {
<div class="layout-grid grid-main"> <div class="layout-grid grid-main">
<div class="side-stack"> <div class="side-stack">
<div class="panel pad" style="box-shadow:none;"> <div class="panel pad" style="box-shadow:none;">
<div class="panel-head"><div><h3>作品与成片</h3><div class="panel-subtitle"></div></div></div> <div class="panel-head"><div><h3>作品与成片</h3><div class="panel-subtitle"></div></div></div>
<div class="list"> <div class="list">
${works.map((video) => ` ${works.map((video) => `
<div class="review-card compact"> <div class="review-card compact">
@@ -7865,7 +7865,7 @@ function openPreferredModelAction() {
const gatewayModels = safeArray(localCatalog.models).map((item) => item.id).filter(Boolean); const gatewayModels = safeArray(localCatalog.models).map((item) => item.id).filter(Boolean);
openActionModal({ openActionModal({
title: "设置分析主模型", title: "设置分析主模型",
description: "后续导入分析、市场调研和风格学习会优先使用这里设置的模型。", description: "导入分析、市场调研和风格学习会优先使用这里设置的模型。",
submitLabel: "保存模型", submitLabel: "保存模型",
fields: [ fields: [
{ {
@@ -8762,7 +8762,7 @@ function openTrackSelectedAccountAction() {
title: trackedItem ? "更新跟踪账号" : "加入跟踪", title: trackedItem ? "更新跟踪账号" : "加入跟踪",
description: trackedItem description: trackedItem
? "这个账号已经在跟踪中,可以切换负责 Agent 或补充备注。" ? "这个账号已经在跟踪中,可以切换负责 Agent 或补充备注。"
: "把当前对标账号加入每日跟踪,后续自动生成更新日报。", : "把当前对标账号加入每日跟踪,系统会自动生成更新日报。",
submitLabel: trackedItem ? "保存跟踪" : "开始跟踪", submitLabel: trackedItem ? "保存跟踪" : "开始跟踪",
fields: [ fields: [
{ name: "accountName", label: "账号", type: "html", html: `<div class="sheet-html"><strong>${escapeHtml(getAccountName(account) || "未命名账号")}</strong><p>${escapeHtml(getAccountProfileUrl(account) || account.signature || "")}</p></div>` }, { name: "accountName", label: "账号", type: "html", html: `<div class="sheet-html"><strong>${escapeHtml(getAccountName(account) || "未命名账号")}</strong><p>${escapeHtml(getAccountProfileUrl(account) || account.signature || "")}</p></div>` },
@@ -9343,7 +9343,7 @@ function openSystemMainPolicyAction() {
const current = bundle.current_version || {}; const current = bundle.current_version || {};
openActionModal({ openActionModal({
title: "编辑系统主 Agent 策略", title: "编辑系统主 Agent 策略",
description: "这是所有用户共享的系统级主 Agent 基座能力,后续用户层和管理员覆盖都会叠加在它上面。", description: "这是所有用户共享的系统级主 Agent 基座能力,用户层和管理员覆盖都会叠加在它上面。",
submitLabel: "保存系统策略", submitLabel: "保存系统策略",
fields: [ fields: [
{ type: "html", label: "当前版本", html: renderPolicyVersionSummary(bundle, "系统主 Agent 还没有系统默认策略。") }, { type: "html", label: "当前版本", html: renderPolicyVersionSummary(bundle, "系统主 Agent 还没有系统默认策略。") },
@@ -10098,7 +10098,7 @@ function openCreateAssistantAction() {
{ name: "name", label: "名称", placeholder: "例如:创业成交助手" }, { name: "name", label: "名称", placeholder: "例如:创业成交助手" },
{ name: "description", label: "说明", placeholder: "例如:服务创业 IP 与成交型短视频" }, { name: "description", label: "说明", placeholder: "例如:服务创业 IP 与成交型短视频" },
{ name: "goal", label: "生成目标", placeholder: "例如:输出创业口播、对标拆解和成交文案" }, { name: "goal", label: "生成目标", placeholder: "例如:输出创业口播、对标拆解和成交文案" },
{ name: "systemPrompt", label: "系统提示词", type: "textarea", rows: 5, placeholder: "可选,不填则后续再补" }, { name: "systemPrompt", label: "系统提示词", type: "textarea", rows: 5, placeholder: "可选,可先留空,后面随时补充" },
{ name: "knowledgeBaseId", label: "默认知识库", type: "select", value: kbOptions[0]?.value || "", options: [{ value: "", label: "暂不绑定" }, ...kbOptions] }, { name: "knowledgeBaseId", label: "默认知识库", type: "select", value: kbOptions[0]?.value || "", options: [{ value: "", label: "暂不绑定" }, ...kbOptions] },
{ name: "modelProfileId", label: "主模型", type: "select", value: modelOptions.find((item) => item.value === safeArray(appState.dashboard?.model_profiles).find((m) => m.is_default)?.id)?.value || modelOptions[0]?.value || "", options: modelOptions } { name: "modelProfileId", label: "主模型", type: "select", value: modelOptions.find((item) => item.value === safeArray(appState.dashboard?.model_profiles).find((m) => m.is_default)?.id)?.value || modelOptions[0]?.value || "", options: modelOptions }
], ],
@@ -10141,7 +10141,7 @@ function openEditAssistantAction(assistantId = "") {
{ name: "name", label: "名称", value: assistant.name || "", placeholder: "例如:创业成交助手" }, { name: "name", label: "名称", value: assistant.name || "", placeholder: "例如:创业成交助手" },
{ name: "description", label: "说明", value: assistant.description || "", placeholder: "例如:服务创业 IP 与成交型短视频" }, { name: "description", label: "说明", value: assistant.description || "", placeholder: "例如:服务创业 IP 与成交型短视频" },
{ name: "goal", label: "生成目标", value: assistant.generation_goal || "", placeholder: "例如:输出创业口播、对标拆解和成交文案" }, { name: "goal", label: "生成目标", value: assistant.generation_goal || "", placeholder: "例如:输出创业口播、对标拆解和成交文案" },
{ name: "systemPrompt", label: "系统提示词", type: "textarea", rows: 5, value: assistant.system_prompt || "", placeholder: "可选,不填则后续再补" }, { name: "systemPrompt", label: "系统提示词", type: "textarea", rows: 5, value: assistant.system_prompt || "", placeholder: "可选,可先留空,后面随时补充" },
{ name: "modelProfileId", label: "主模型", type: "select", value: assistant.model_profile_id || modelOptions[0]?.value || "", options: modelOptions } { name: "modelProfileId", label: "主模型", type: "select", value: assistant.model_profile_id || modelOptions[0]?.value || "", options: modelOptions }
], ],
onSubmit: async (values) => { onSubmit: async (values) => {
@@ -10289,7 +10289,7 @@ function openBenchmarkLinkAction(defaults = {}) {
: null; : null;
openActionModal({ openActionModal({
title: "保存对标关系", title: "保存对标关系",
description: "把当前账号和另一个账号关联成对标关系,便于后续持续跟踪。", description: "把当前账号和另一个账号关联成对标关系,便于持续跟踪。",
submitLabel: "保存关系", submitLabel: "保存关系",
fields: [ fields: [
{ name: "targetAccountId", label: "目标账号", type: "select", value: defaults.targetAccountId || candidate?.candidate_account_id || options[0]?.value || "", options: [{ value: "", label: "仅保存主页链接" }, ...options] }, { name: "targetAccountId", label: "目标账号", type: "select", value: defaults.targetAccountId || candidate?.candidate_account_id || options[0]?.value || "", options: [{ value: "", label: "仅保存主页链接" }, ...options] },
@@ -11220,7 +11220,7 @@ function openReviewAction(defaults = {}) {
title: existingReview ? "编辑复盘" : "写复盘", title: existingReview ? "编辑复盘" : "写复盘",
description: existingReview description: existingReview
? "补充表现数据、判断和下一步动作,持续迭代项目策略。" ? "补充表现数据、判断和下一步动作,持续迭代项目策略。"
: "把完成任务写成一条可追踪复盘,后续可按项目累计。", : "把完成任务写成一条可追踪复盘,可按项目累计。",
submitLabel: existingReview ? "保存复盘" : "创建复盘", submitLabel: existingReview ? "保存复盘" : "创建复盘",
fields: [ fields: [
{ name: "title", label: "标题", value: existingReview?.title || defaults.title || sourceJob?.title || "", placeholder: "例如:创业口播 3 月 22 日复盘" }, { name: "title", label: "标题", value: existingReview?.title || defaults.title || sourceJob?.title || "", placeholder: "例如:创业口播 3 月 22 日复盘" },
@@ -11891,20 +11891,11 @@ document.addEventListener("click", async (event) => {
return; return;
} }
if (name === "select-project") { if (name === "select-project") {
appState.selectedProjectId = action.dataset.projectId || "";
setBusy(true, "正在切换项目视图...");
try { try {
await loadStorageStatus(appState.selectedProjectId || ""); await applySelectedProject(action.dataset.projectId || "");
await loadAgentControlSurfaces(appState.selectedProjectId || ""); } catch (error) {
if (appState.selectedOnelinerSessionId) { presentActionFailure(error, "切换项目失败");
await loadOneLinerMessages(appState.selectedOnelinerSessionId);
} else {
appState.onelinerMessages = [];
}
} finally {
setBusy(false, "");
} }
renderAll();
return; return;
} }
if (name === "select-platform") { if (name === "select-platform") {

View File

@@ -135,6 +135,7 @@ test("project creation and switching use in-app sheets instead of browser prompt
test("mobile project sheets support direct project picking and zoom-safe form controls", () => { test("mobile project sheets support direct project picking and zoom-safe form controls", () => {
const projectSwitcher = extractBetween(APP, "function openDashboardProjectSwitcher()", "function openDashboardActionReasonAction("); const projectSwitcher = extractBetween(APP, "function openDashboardProjectSwitcher()", "function openDashboardActionReasonAction(");
const applySelectedProject = extractBetween(APP, "async function applySelectedProject(projectId = \"\")", "function openDashboardProjectSwitcher()"); const applySelectedProject = extractBetween(APP, "async function applySelectedProject(projectId = \"\")", "function openDashboardProjectSwitcher()");
const clickHandler = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {");
const createProject = extractBetween(APP, "async function createProject()", "function openPreferredModelAction()"); const createProject = extractBetween(APP, "async function createProject()", "function openPreferredModelAction()");
assert.match(APP, /async function applySelectedProject\(projectId = ""\)/); assert.match(APP, /async function applySelectedProject\(projectId = ""\)/);
assert.match(APP, /function focusDashboardWorkspace\(anchorId = "dashboard-workspace-anchor"\)/); assert.match(APP, /function focusDashboardWorkspace\(anchorId = "dashboard-workspace-anchor"\)/);
@@ -150,6 +151,7 @@ test("mobile project sheets support direct project picking and zoom-safe form co
assert.match(applySelectedProject, /loadStorageStatus\(appState\.selectedProjectId \|\| ""\)/); assert.match(applySelectedProject, /loadStorageStatus\(appState\.selectedProjectId \|\| ""\)/);
assert.match(applySelectedProject, /loadAgentControlSurfaces\(appState\.selectedProjectId \|\| ""\)/); assert.match(applySelectedProject, /loadAgentControlSurfaces\(appState\.selectedProjectId \|\| ""\)/);
assert.match(applySelectedProject, /focusDashboardWorkspace\("dashboard-workspace-anchor"\)/); assert.match(applySelectedProject, /focusDashboardWorkspace\("dashboard-workspace-anchor"\)/);
assert.match(clickHandler, /name === "select-project"[\s\S]*await applySelectedProject\(action\.dataset\.projectId \|\| ""\)/);
assert.match(APP, /id="dashboard-workspace-anchor"/); assert.match(APP, /id="dashboard-workspace-anchor"/);
assert.match(createProject, /onOpen:\s*\(/); assert.match(createProject, /onOpen:\s*\(/);
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.field-stack input,\s*[\s\S]*\.field-stack textarea,\s*[\s\S]*\.field-stack select\s*\{[\s\S]*min-height:\s*46px/); assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.field-stack input,\s*[\s\S]*\.field-stack textarea,\s*[\s\S]*\.field-stack select\s*\{[\s\S]*min-height:\s*46px/);
@@ -385,6 +387,17 @@ test("discovery and production screens expose compact mobile flow summaries", ()
assert.match(production, /失败/); assert.match(production, /失败/);
}); });
test("workbench copy uses direct live language instead of future-placeholder wording", () => {
assert.doesNotMatch(APP, /可选,不填则后续再补/);
assert.match(APP, /可选,可先留空,后面随时补充/);
assert.match(APP, /文案生成、对标绑定和复盘都会优先使用这里选中的 Agent/);
assert.match(APP, /导入分析、市场调研和风格学习都会优先使用这里设置的模型/);
assert.match(APP, /系统会自动生成更新日报/);
assert.match(APP, /把完成任务写成一条可追踪复盘,可按项目累计/);
assert.match(APP, /先绑定执行 Agent再完善任务目标和方法论/);
assert.match(APP, /先看真实作品,再继续整理文档与成片/);
});
test("discovery and production screens expose mobile focus cards with next-step actions", () => { test("discovery and production screens expose mobile focus cards with next-step actions", () => {
const discovery = extractBetween(APP, "function renderDiscoveryScreen()", "function renderTrackingScreen()"); const discovery = extractBetween(APP, "function renderDiscoveryScreen()", "function renderTrackingScreen()");
const production = extractBetween(APP, "function renderProductionScreen()", "function renderReviewScreen()"); const production = extractBetween(APP, "function renderProductionScreen()", "function renderReviewScreen()");