完善配方驱动的创作编排
为可重复技能补充参数校验与展示,统一配方、编排器和执行单元术语。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -27,12 +27,12 @@ const MSG_CLASS = {
|
||||
const MSG_LABEL = {
|
||||
user_input: "你",
|
||||
agent_tool: "工具",
|
||||
orchestrator_decision: "导演",
|
||||
orchestrator_thinking: "导演 · 思考",
|
||||
orchestrator_prompt: "导演",
|
||||
orchestrator_assessment: "导演 · 内容评价",
|
||||
worker_running: "Worker",
|
||||
worker_output: "Worker",
|
||||
orchestrator_decision: "编排器",
|
||||
orchestrator_thinking: "编排器 · 思考",
|
||||
orchestrator_prompt: "编排器",
|
||||
orchestrator_assessment: "编排器 · 内容评价",
|
||||
worker_running: "执行单元",
|
||||
worker_output: "执行单元",
|
||||
worker_questions: "提问",
|
||||
error: "错误",
|
||||
system_info: "系统",
|
||||
@@ -924,7 +924,7 @@ function renderCreationFlowView(flowView) {
|
||||
return `<div class="review-parse-error" role="alert">
|
||||
<div class="review-parse-error-title">流程无法解析</div>
|
||||
<p>${esc(flowView.parseError)}</p>
|
||||
<p class="review-parse-error-hint">需要 JSON:steps 数组,每步含 name(与可选 id)与 depends_on;可含 status=open|closed。</p>
|
||||
<p class="review-parse-error-hint">需要 JSON:steps 数组,每步含 name(与可选 id)、depends_on、可选 params;可含 status=open|closed。</p>
|
||||
</div>`;
|
||||
}
|
||||
if (!flowView.steps?.length) return "";
|
||||
@@ -957,9 +957,19 @@ function renderCreationFlowView(flowView) {
|
||||
s.id && s.id !== s.name
|
||||
? `${esc(s.name)} <span class="flow-id">(${esc(s.id)})</span>`
|
||||
: esc(s.name);
|
||||
const paramsText = formatFlowParams(s.params);
|
||||
const paramsMissing =
|
||||
s.paramsMissing?.length > 0
|
||||
? `<span class="flow-params-missing">缺参:${esc(s.paramsMissing.join("、"))}</span>`
|
||||
: "";
|
||||
const paramsHtml = paramsText
|
||||
? `<span class="flow-params">${esc(paramsText)}</span>`
|
||||
: "";
|
||||
return `<li class="flow-step">
|
||||
<span class="flow-order">${esc(String(s.order))}</span>
|
||||
<span class="flow-name">${nameLabel}${occ}</span>
|
||||
${paramsHtml}
|
||||
${paramsMissing}
|
||||
<span class="flow-deps">依赖:${deps}</span>
|
||||
</li>`;
|
||||
})
|
||||
@@ -973,6 +983,14 @@ function renderCreationFlowView(flowView) {
|
||||
</section>`;
|
||||
}
|
||||
|
||||
function formatFlowParams(params) {
|
||||
if (!params || typeof params !== "object" || Array.isArray(params)) return "";
|
||||
const parts = Object.entries(params)
|
||||
.filter(([, v]) => v != null && String(v).trim() !== "")
|
||||
.map(([k, v]) => `${k}=${typeof v === "string" ? v : JSON.stringify(v)}`);
|
||||
return parts.length ? parts.join(" · ") : "";
|
||||
}
|
||||
|
||||
function renderReviewFeedCard(review) {
|
||||
const flowHtml = review.creationFlowView
|
||||
? renderCreationFlowView(review.creationFlowView)
|
||||
@@ -1133,9 +1151,9 @@ export function renderMessageFeed(view, loading, handlers = {}) {
|
||||
p.textContent = "游玩模式:Agent 将按 Worker 集调度,推进世界与叙事。";
|
||||
} else if (view.uiPrompt) {
|
||||
const recipeLine = view.selectedRecipe?.name
|
||||
? `\n\n已选导演:${view.selectedRecipe.name}`
|
||||
? `\n\n已选配方:${view.selectedRecipe.name}`
|
||||
: view.recipes?.length
|
||||
? "\n\n(请先在新建作品时选定导演)"
|
||||
? "\n\n(请先在新建作品时选定配方)"
|
||||
: "";
|
||||
p.textContent = `${view.uiPrompt}${recipeLine}`;
|
||||
p.classList.add("empty-intake");
|
||||
|
||||
10
web/app.js
10
web/app.js
@@ -28,7 +28,7 @@ const $ = (id) => document.getElementById(id);
|
||||
|
||||
const PHASE = { idle: "待命", running: "执行中", waiting_user: "等待你", done: "已完成", error: "出错" };
|
||||
const REASON = {
|
||||
skill_selection: "选择能力包",
|
||||
skill_selection: "选择配方",
|
||||
intake: "补充信息",
|
||||
input: "等待输入",
|
||||
approve_step: "确认执行",
|
||||
@@ -748,7 +748,7 @@ function renderHeader(view, loading) {
|
||||
$("work-title").textContent = view.bookTitle ?? "未命名作品";
|
||||
const skill = displaySkillPackLabel(view.activeSkill) ||
|
||||
view.selectedRecipe?.name ||
|
||||
"导演";
|
||||
"配方";
|
||||
const phase = view.waitingReason
|
||||
? REASON[view.waitingReason.kind] ?? view.phase
|
||||
: PHASE[view.phase] ?? view.phase;
|
||||
@@ -988,10 +988,10 @@ async function populateDirectorSelect() {
|
||||
const directors = data.directors ?? [];
|
||||
sel.innerHTML = "";
|
||||
if (!directors.length) {
|
||||
sel.innerHTML = `<option value="">暂无导演</option>`;
|
||||
sel.innerHTML = `<option value="">暂无配方</option>`;
|
||||
if (desc) {
|
||||
desc.hidden = false;
|
||||
desc.textContent = "尚未配置导演选项(recipes/catalog.yaml)。";
|
||||
desc.textContent = "尚未配置配方选项(recipes/catalog.yaml)。";
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1027,7 +1027,7 @@ async function createBook() {
|
||||
const title = $("input-book-title").value.trim() || "未命名作品";
|
||||
const recipeId = $("select-director")?.value?.trim();
|
||||
if (!recipeId) {
|
||||
alert("请选择导演");
|
||||
alert("请选择配方");
|
||||
return;
|
||||
}
|
||||
$("btn-create-book").disabled = true;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* 与 docs/ui-glossary.md、src/server/display-labels.ts 保持同步。
|
||||
* 用户侧拍摄术语:导演 / 剧本 / 演员 / 能力。
|
||||
* 用户侧口径:配方 / 编排器 / 技能 / 工作流计划 / 运行规格 / 执行单元。
|
||||
*/
|
||||
|
||||
const STAGE_LABELS = {
|
||||
@@ -15,15 +15,15 @@ const STAGE_LABELS = {
|
||||
|
||||
const WORKER_LABELS = {
|
||||
"design-core": "创作 · 核心",
|
||||
"design-worker": "创作 · 演员规格",
|
||||
"design-worker": "创作 · 执行单元规格",
|
||||
"design-fixed": "创作 · 固定上下文",
|
||||
"design-refine": "创作 · 细化与终稿",
|
||||
"design-intake": "创作 · 综合收口",
|
||||
"design-flow": "创作 · 流程编排",
|
||||
"design-step": "创作 · 执行步骤",
|
||||
"opening-generator": "开局 · 开场白",
|
||||
orchestrator: "导演",
|
||||
"agent-burst": "导演调度",
|
||||
orchestrator: "编排器",
|
||||
"agent-burst": "编排器调度",
|
||||
narrator: "叙事转述",
|
||||
"role-decide": "角色决策",
|
||||
"world-simulator": "世界推演",
|
||||
@@ -72,11 +72,11 @@ export function displayWorkerLabel(id) {
|
||||
}
|
||||
if (trimmed.startsWith("worker:")) {
|
||||
const ref = trimmed.slice("worker:".length);
|
||||
return `演员 · ${displayWorkerLabel(ref)}`;
|
||||
return `执行单元 · ${displayWorkerLabel(ref)}`;
|
||||
}
|
||||
if (trimmed.startsWith("fixed:")) {
|
||||
const topic = trimmed.slice("fixed:".length);
|
||||
return `能力 · ${FIXED_TOPIC_LABELS[topic] ?? topic}`;
|
||||
return `技能 · ${FIXED_TOPIC_LABELS[topic] ?? topic}`;
|
||||
}
|
||||
if (trimmed.startsWith("resident:")) {
|
||||
return `常驻 · ${trimmed.slice("resident:".length)}`;
|
||||
@@ -85,7 +85,7 @@ export function displayWorkerLabel(id) {
|
||||
}
|
||||
|
||||
export function formatWorkerDisplayTitle(workerId, action = null) {
|
||||
const base = displayWorkerLabel(workerId) || "演员";
|
||||
const base = displayWorkerLabel(workerId) || "执行单元";
|
||||
if (action === "output") return `${base} · 产出`;
|
||||
if (action === "questions") return `${base} · 提问`;
|
||||
if (action === "stub") return `${base} · 占位`;
|
||||
|
||||
@@ -13,12 +13,12 @@ function formatExportTime(iso) {
|
||||
|
||||
const KIND_LABELS = {
|
||||
user_input: "用户输入",
|
||||
orchestrator_decision: "导演决策",
|
||||
orchestrator_prompt: "导演询问",
|
||||
agent_tool: "Agent Tool",
|
||||
worker_running: "Worker 执行",
|
||||
worker_output: "Worker 产出",
|
||||
worker_stub: "Worker 占位",
|
||||
orchestrator_decision: "编排器决策",
|
||||
orchestrator_prompt: "编排器询问",
|
||||
agent_tool: "工具",
|
||||
worker_running: "执行单元 · 执行",
|
||||
worker_output: "执行单元 · 产出",
|
||||
worker_stub: "执行单元 · 占位",
|
||||
system_info: "系统",
|
||||
error: "错误",
|
||||
};
|
||||
@@ -39,7 +39,7 @@ export function sessionToMarkdown(view) {
|
||||
|
||||
if (view.skillCatalog?.length) {
|
||||
const stageLabel = view.lifecycleStage === "play" ? "游玩" : "创作";
|
||||
lines.push(`## ${stageLabel}能力清单`);
|
||||
lines.push(`## ${stageLabel}技能清单`);
|
||||
lines.push("");
|
||||
for (const skill of view.skillCatalog) {
|
||||
const mark =
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="main-head-right">
|
||||
<a class="link" href="/modules.html">能力</a>
|
||||
<a class="link" href="/modules.html">技能</a>
|
||||
<a class="link" href="/stats.html">Token</a>
|
||||
<a class="link" href="/settings.html">设置</a>
|
||||
<button type="button" class="btn-sm" id="btn-save-instance" hidden>保存定稿</button>
|
||||
@@ -88,9 +88,9 @@
|
||||
<dialog id="dialog-new-book" class="dialog">
|
||||
<form method="dialog" id="form-new-book">
|
||||
<h2>新建作品</h2>
|
||||
<p class="dialog-desc">选择导演,再起名。导演是本局方法起点,之后按对话谈成剧本(可调味)。</p>
|
||||
<p class="dialog-desc">选择配方,再起名。配方是本局方法起点,之后按对话谈成工作流计划与运行规格(可调味)。</p>
|
||||
<label class="field">
|
||||
导演
|
||||
配方
|
||||
<select id="select-director" required>
|
||||
<option value="">加载中…</option>
|
||||
</select>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* 能力池浏览页 — 叠在 settings.css 壳上 */
|
||||
/* 技能池浏览页 — 叠在 settings.css 壳上 */
|
||||
|
||||
.mod-shell {
|
||||
max-width: 1280px;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>能力池 · Writing Agent</title>
|
||||
<title>技能池 · Writing Agent</title>
|
||||
<link rel="stylesheet" href="/settings.css" />
|
||||
<link rel="stylesheet" href="/modules.css" />
|
||||
</head>
|
||||
@@ -12,15 +12,15 @@
|
||||
<a class="st-brand" href="/">Writing Agent</a>
|
||||
<nav class="st-nav" aria-label="主导航">
|
||||
<a href="/">对话</a>
|
||||
<a href="/modules.html" aria-current="page">能力</a>
|
||||
<a href="/modules.html" aria-current="page">技能</a>
|
||||
<a href="/stats.html">Token</a>
|
||||
<a href="/settings.html">设置</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div class="st-shell mod-shell">
|
||||
<aside class="st-rail mod-rail" aria-label="能力列表">
|
||||
<p class="st-rail-label">备选能力</p>
|
||||
<aside class="st-rail mod-rail" aria-label="技能列表">
|
||||
<p class="st-rail-label">备选技能</p>
|
||||
<p class="mod-pack" id="pack-label">加载中…</p>
|
||||
<div class="mod-filter">
|
||||
<input
|
||||
@@ -38,14 +38,14 @@
|
||||
</div>
|
||||
<nav class="mod-list" id="mod-list"></nav>
|
||||
<p class="st-rail-note">
|
||||
导演编排剧本时从这里选型;本页只读浏览方法正文。
|
||||
编排器编排工作流计划时从这里选型;本页只读浏览方法正文。
|
||||
</p>
|
||||
</aside>
|
||||
|
||||
<main class="st-main mod-main">
|
||||
<header class="st-main-head">
|
||||
<div>
|
||||
<h1 id="panel-title">能力池</h1>
|
||||
<h1 id="panel-title">技能池</h1>
|
||||
<p class="st-sub" id="panel-subtitle">选择左侧一项查看方法块</p>
|
||||
</div>
|
||||
<div class="st-main-actions">
|
||||
@@ -57,7 +57,7 @@
|
||||
<p class="mod-error" id="mod-error" hidden></p>
|
||||
|
||||
<div class="st-body mod-body" id="mod-detail">
|
||||
<p class="mod-empty">从左侧选择一项能力</p>
|
||||
<p class="mod-empty">从左侧选择一项技能</p>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -193,7 +193,7 @@ async function boot() {
|
||||
const data = await res.json();
|
||||
modulesCache = data.modules || [];
|
||||
document.getElementById("pack-label").textContent =
|
||||
`导演包 · ${data.skillPackId || "—"} · ${modulesCache.length} 项`;
|
||||
`配方包 · ${data.skillPackId || "—"} · ${modulesCache.length} 项`;
|
||||
renderList();
|
||||
|
||||
const fromHash = decodeURIComponent((location.hash || "").replace(/^#/, ""));
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<a class="st-brand" href="/">Writing Agent</a>
|
||||
<nav class="st-nav" aria-label="主导航">
|
||||
<a href="/">对话</a>
|
||||
<a href="/modules.html">能力</a>
|
||||
<a href="/modules.html">技能</a>
|
||||
<a href="/stats.html">Token</a>
|
||||
<a href="/settings.html" aria-current="page">设置</a>
|
||||
</nav>
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
<body>
|
||||
<div class="stats-page">
|
||||
<header>
|
||||
<p><a href="/">← 对话</a> · <a href="/modules.html">能力</a> · <a href="/settings.html">设置</a></p>
|
||||
<p><a href="/">← 对话</a> · <a href="/modules.html">技能</a> · <a href="/settings.html">设置</a></p>
|
||||
<h1>Token 统计</h1>
|
||||
<p class="stats-muted">按时间戳筛选调用记录(存于本地 stats/*.jsonl)</p>
|
||||
</header>
|
||||
|
||||
@@ -569,10 +569,16 @@ body[data-lifecycle="play"] .panel-agent { background: #f7fef9; }
|
||||
.flow-name {
|
||||
font-weight: 600;
|
||||
}
|
||||
.flow-params,
|
||||
.flow-params-missing,
|
||||
.flow-deps {
|
||||
grid-column: 2;
|
||||
font-size: 12px;
|
||||
color: var(--muted, #666);
|
||||
}
|
||||
.flow-params-missing {
|
||||
color: var(--danger, #b42318);
|
||||
}
|
||||
|
||||
.board-panel {
|
||||
flex: 1;
|
||||
|
||||
Reference in New Issue
Block a user