文案(智能体):停止 / 方案 / 子会话 / 压缩上下文等按术语表与 human-copy 重写
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
0338037a96
commit
775d1589e4
16 files changed
+49
-49
No files matched your search
@@ -86,7 +86,7 @@ export const BUDGET_LABEL: Record<string, string> = {
|
||||
const COMPACT_STATUS: Record<string, string> = {
|
||||
completed: "已完成",
|
||||
declined: "没有压缩",
|
||||
aborted: "已中止",
|
||||
aborted: "已停止",
|
||||
failed: "失败",
|
||||
};
|
||||
|
||||
@@ -108,7 +108,7 @@ export function contextLabel(context: string | null): string | null {
|
||||
const first = context.split(" · ")[0] ?? "";
|
||||
// 前缀里第一段是界面给的名称(「作品:沙丘2」);没有名称时只剩 key=ID,不上屏
|
||||
if (!first || first.includes("=")) return null;
|
||||
return first.replace(/^(作品|资源|下载|整理记录|站点|任务|引擎任务)[::]\s*/, "");
|
||||
return first.replace(/^(作品|资源|下载|整理记录|站点|任务|引擎任务|后台任务)[::]\s*/, "");
|
||||
}
|
||||
|
||||
export function UserMessage({
|
||||
@@ -289,7 +289,7 @@ export function StepSummary({
|
||||
const thinking = activity.some((a) => a.kind === "thinking");
|
||||
const head = current
|
||||
? `${done ? `${done},` : ""}${runningText(current)}…`
|
||||
: done || (running ? "思考中…" : thinking ? "想了一下" : "过程说明");
|
||||
: done || (running ? "思考中…" : thinking ? "思考过程" : "过程说明");
|
||||
return (
|
||||
<div className="w-full min-w-0" data-slot="step-summary">
|
||||
<button
|
||||
@@ -365,7 +365,7 @@ export function DeniedLines({ runId, calls }: { runId: string; calls: ToolCallVi
|
||||
{
|
||||
onSuccess: () => {
|
||||
setSent((s) => new Set(s).add(c.capabilityId));
|
||||
toast.success("已批准,智能体重试这一步");
|
||||
toast.success("已批准,智能体会重试这一步");
|
||||
},
|
||||
onError: (e) => toast.error(errorMessage(e)),
|
||||
},
|
||||
@@ -399,7 +399,7 @@ function EndNotice({ item }: { item: Extract<StreamItem, { kind: "end" }> }) {
|
||||
<CircleSlash aria-hidden className="mt-0.5 size-3.5 shrink-0" />
|
||||
<span className="min-w-0 break-words">
|
||||
{budget
|
||||
? `超出预算(${BUDGET_LABEL[item.budget ?? ""] ?? "用量"}),已停止。已完成的动作不会撤销;调大预算后发消息可以继续。`
|
||||
? `超出预算(${BUDGET_LABEL[item.budget ?? ""] ?? "用量"}),已停止。已完成的操作不会撤销,调大预算后发消息可继续`
|
||||
: aborted
|
||||
? "已停止。"
|
||||
: `运行失败${item.message ? `:${humanizeReason(item.message) ?? item.message}` : ""}`}
|
||||
@@ -523,7 +523,7 @@ export function AgentConversation({
|
||||
<UserMessage text={run.goal} tag={isProposeFirst(run) ? "先给方案" : undefined} />
|
||||
{blocks.map((b, bi) =>
|
||||
b.kind === "user" ? (
|
||||
<UserMessage key={b.key} text={b.text} note={b.pending ? "已送达,等待智能体读取" : undefined} animateIn />
|
||||
<UserMessage key={b.key} text={b.text} note={b.pending ? "已发送,等智能体读取" : undefined} animateIn />
|
||||
) : (
|
||||
<section key={b.key} aria-label="智能体回合" className="flex w-full min-w-0 flex-col items-start gap-2">
|
||||
{b.parts.map((p, pi) => {
|
||||
@@ -587,7 +587,7 @@ export function AgentConversation({
|
||||
return (
|
||||
<MessageMarker key={p.key}>
|
||||
<Layers aria-hidden className="size-3.5 shrink-0" />
|
||||
整理上下文 ·{" "}
|
||||
压缩上下文 ·{" "}
|
||||
{p.item.done ? (COMPACT_STATUS[p.item.status ?? "completed"] ?? p.item.status) : "进行中…"}
|
||||
</MessageMarker>
|
||||
);
|
||||
@@ -626,7 +626,7 @@ function DraftCheckCard({ jobId, name }: { jobId: string; name: string }) {
|
||||
<CalendarCog aria-hidden className="size-4 shrink-0 text-muted-foreground" />
|
||||
巡检草稿「{name}」<StatusBadge>未启用</StatusBadge>
|
||||
</p>
|
||||
<p className="mt-0.5 text-meta text-muted-foreground">到计划页检查目标、间隔和能力,确认后打开开关启用。</p>
|
||||
<p className="mt-0.5 text-meta text-muted-foreground">在计划页检查目标、间隔和能力后启用。</p>
|
||||
</div>
|
||||
<Button asChild size="sm" variant="outline">
|
||||
<Link to={`${consolePath("schedules")}?job=${encodeURIComponent(jobId)}`}>检查并启用</Link>
|
||||
@@ -672,8 +672,8 @@ export function resultFacts(report: GoalReport | null): ResultFact[] {
|
||||
});
|
||||
}
|
||||
if (r.imported && r.imported !== done.length) facts.push({ text: `已整理 ${r.imported} 个` });
|
||||
if (r.inProgress) facts.push({ text: `${r.inProgress} 个还在进行` });
|
||||
if (r.failed) facts.push({ text: `${r.failed} 个没有成功`, tone: "warning" });
|
||||
if (r.inProgress) facts.push({ text: `${r.inProgress} 个进行中` });
|
||||
if (r.failed) facts.push({ text: `${r.failed} 个失败`, tone: "warning" });
|
||||
return facts.length ? facts : [{ text: "没有提交下载或整理" }];
|
||||
}
|
||||
|
||||
@@ -859,7 +859,7 @@ export function RunAnnouncer({ status, finished }: { status: AgentRunStatus; fin
|
||||
const [msg, setMsg] = useState("");
|
||||
const prev = useRef(status);
|
||||
useEffect(() => {
|
||||
if (prev.current !== status) setMsg(`运行${RUN_STATUS_LABEL[status]}`);
|
||||
if (prev.current !== status) setMsg(`会话${RUN_STATUS_LABEL[status]}`);
|
||||
prev.current = status;
|
||||
}, [status]);
|
||||
useEffect(() => {
|
||||
|
||||
@@ -51,7 +51,7 @@ export function useContextName(ctx: AgentContext | null): string | null {
|
||||
const task = useDownloadTask(path ? ctx?.downloadTaskId : undefined);
|
||||
const record = useRecord(path ? ctx?.recordId : undefined);
|
||||
if (!ctx) return null;
|
||||
if (!path) return ctx.label ? ctx.label.replace(/^(作品|资源|下载|整理记录|站点|任务)[::]\s*/, "") : null;
|
||||
if (!path) return ctx.label ? ctx.label.replace(/^(作品|资源|下载|整理记录|站点|任务|后台任务)[::]\s*/, "") : null;
|
||||
if (ctx.entityId) return entity.data?.entity.title ?? "这部作品";
|
||||
if (ctx.siteId) return site.data?.name ?? ctx.siteId;
|
||||
if (ctx.downloadTaskId) return task.data?.name ?? "这个下载任务";
|
||||
@@ -162,8 +162,8 @@ export function AgentDrawer() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
aria-label="最小化(运行继续)"
|
||||
title="最小化(运行继续)"
|
||||
aria-label="最小化,会话继续运行"
|
||||
title="最小化,会话继续运行"
|
||||
onClick={minimizeAgentDrawer}
|
||||
>
|
||||
<Minus />
|
||||
@@ -256,8 +256,8 @@ function AgentMiniDock({ runId }: { runId: string }) {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
aria-label="关闭小窗(运行在后台继续)"
|
||||
title="关闭小窗(运行在后台继续)"
|
||||
aria-label="关闭小窗,会话在后台继续"
|
||||
title="关闭小窗,会话在后台继续"
|
||||
onClick={() => setAgentDrawerOpen(false)}
|
||||
>
|
||||
<X />
|
||||
|
||||
@@ -143,7 +143,7 @@ export function OptionsCard({ runId, view, waiting }: { runId: string; view: Opt
|
||||
const risks = options.flatMap((o) => o.risks.map((r) => ({ key: `${o.id}:${r}`, option: o.title, text: r })));
|
||||
const submit = (optionId: string | null) => {
|
||||
if (optionId === null && !reason.trim()) {
|
||||
toast.error("写一句不要的原因,智能体按它调整");
|
||||
toast.error("写一下为什么都不要");
|
||||
return;
|
||||
}
|
||||
setPicking(optionId ?? "__none__");
|
||||
@@ -153,7 +153,7 @@ export function OptionsCard({ runId, view, waiting }: { runId: string; view: Opt
|
||||
onSuccess: () => {
|
||||
setSent({ optionId, reason: reason.trim() });
|
||||
const o = view.options.find((x) => x.id === optionId);
|
||||
toast.success(optionId ? (o?.action?.approves ? "已选定并批准" : "已选定") : "已退回,智能体调整方案");
|
||||
toast.success(optionId ? (o?.action?.approves ? "已选定并批准" : "已选定") : "已发送,智能体会重新给方案");
|
||||
},
|
||||
onError: (e) => toast.error(errorMessage(e)),
|
||||
onSettled: () => setPicking(null),
|
||||
@@ -286,7 +286,7 @@ export function OptionsCard({ runId, view, waiting }: { runId: string; view: Opt
|
||||
disabled={!reason.trim() || choose.isPending}
|
||||
onClick={() => submit(null)}
|
||||
>
|
||||
退回
|
||||
发送
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -336,7 +336,7 @@ export function InlineApproval({ view, stepTitle }: { view: ApprovalView; stepTi
|
||||
...(s === "rejected" && reason.trim() ? { reason: reason.trim() } : {}),
|
||||
},
|
||||
{
|
||||
onSuccess: () => toast.success(s === "approved" ? "已批准,智能体继续执行" : "已拒绝,智能体按理由调整"),
|
||||
onSuccess: () => toast.success(s === "approved" ? "已批准,智能体继续执行" : "已拒绝,智能体会调整做法"),
|
||||
onError: (e) => toast.error(errorMessage(e)),
|
||||
},
|
||||
);
|
||||
@@ -364,7 +364,7 @@ export function InlineApproval({ view, stepTitle }: { view: ApprovalView; stepTi
|
||||
value={reason}
|
||||
maxLength={2000}
|
||||
aria-label="拒绝理由"
|
||||
placeholder="理由或建议(可选),智能体按它调整"
|
||||
placeholder="理由或建议,可不填"
|
||||
onChange={(e) => setReason(e.target.value)}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -119,7 +119,7 @@ export function GoalComposer({
|
||||
maxRows={10}
|
||||
maxLength={4000}
|
||||
aria-label="目标"
|
||||
placeholder="例如:把沙丘:第二部的 4K 收进来"
|
||||
placeholder="例如:下载沙丘 2 的 4K 版本"
|
||||
submitLabel={pending ? "提交中" : "开始"}
|
||||
commands={NEW_RUN_COMMANDS}
|
||||
context={context}
|
||||
@@ -491,7 +491,7 @@ export function AgentRunPanel({
|
||||
maxRows={6}
|
||||
maxLength={8000}
|
||||
aria-label="发消息"
|
||||
placeholder={ended ? "接着说,输入 / 看指令" : "补充或纠正"}
|
||||
placeholder={ended ? "继续对话,输入 / 查看指令" : "补充或纠正"}
|
||||
commands={RUN_COMMANDS}
|
||||
/>
|
||||
</div>
|
||||
@@ -499,13 +499,13 @@ export function AgentRunPanel({
|
||||
<ConfirmDialog
|
||||
open={confirmStop}
|
||||
onOpenChange={setConfirmStop}
|
||||
title="停止这次运行?"
|
||||
description="后续步骤不再执行,已完成的动作(如已提交的下载)不会撤销。"
|
||||
confirmLabel="停止运行"
|
||||
title="停止这个会话?"
|
||||
description="后续步骤不再执行。已完成的操作不会撤销,已提交的下载会继续。"
|
||||
confirmLabel="停止会话"
|
||||
destructive
|
||||
onConfirm={() =>
|
||||
abort.mutateAsync(r.id).then(
|
||||
() => toast.success("已请求停止"),
|
||||
() => toast.success("正在停止"),
|
||||
(e) => {
|
||||
toast.error(errorMessage(e));
|
||||
throw e;
|
||||
|
||||
@@ -380,7 +380,7 @@ export function AgentSessionList({
|
||||
? "没有等你处理的会话"
|
||||
: "没有符合条件的会话"
|
||||
: f.group === "auto"
|
||||
? "还没有巡检会话,巡检到点后会出现在这里"
|
||||
? "还没有巡检会话"
|
||||
: "还没有会话,写一个目标开始"
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -245,7 +245,7 @@ describe("拒绝:缩成一行", () => {
|
||||
it("授权拒绝 → 需要你批准 + [批准并重试];白名单拦截没有按钮", () => {
|
||||
expect(deniedReason(denied)).toEqual({ text: "需要你批准", approvable: true });
|
||||
expect(deniedReason(call({ status: "denied", reason: "不在白名单" }))).toEqual({
|
||||
text: "本次没有开放",
|
||||
text: "这次会话没开放这个能力",
|
||||
approvable: false,
|
||||
});
|
||||
const t = text(
|
||||
@@ -529,7 +529,7 @@ describe("实际结果行(FIX-9):只列系统核实的事实", () => {
|
||||
const html = render(
|
||||
createElement(ResultLine, { run: RUN, report: done, live: withText("提交了 2 个下载,整理了 1 个。") }),
|
||||
);
|
||||
expect(text(html).replace(/\s*、\s*/g, "、")).toContain("提交了 2 个下载、完成 1 次整理、1 个还在进行 · 41 秒");
|
||||
expect(text(html).replace(/\s*、\s*/g, "、")).toContain("提交了 2 个下载、完成 1 次整理、1 个进行中 · 41 秒");
|
||||
expect(html).toContain(`href="/console/organize/${ID(3)}"`);
|
||||
expect(html).toContain('href="/console/downloads"');
|
||||
expect(html).not.toContain("与汇报不一致");
|
||||
|
||||
@@ -180,7 +180,7 @@ export function contextFromPath(pathname: string): AgentContext | null {
|
||||
case "organize":
|
||||
return { label: `整理记录 ${short}`, recordId: id, fromPath: true };
|
||||
case "engine":
|
||||
return { label: `引擎任务 ${short}`, taskId: id, fromPath: true };
|
||||
return { label: `后台任务 ${short}`, taskId: id, fromPath: true };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -46,9 +46,9 @@ function budgetText(b: Record<string, number | undefined>) {
|
||||
const parts: string[] = [];
|
||||
if (b.maxToolCalls !== undefined) parts.push(`${b.maxToolCalls} 次调用`);
|
||||
if (b.maxTokens !== undefined) parts.push(`${tokens(b.maxTokens)} token`);
|
||||
if (b.maxDurationSec !== undefined) parts.push(`${b.maxDurationSec}s`);
|
||||
if (b.maxDurationSec !== undefined) parts.push(`${b.maxDurationSec} 秒`);
|
||||
if (b.maxCostUsd !== undefined) parts.push(`$${b.maxCostUsd}`);
|
||||
return parts.length ? parts.join(" · ") : "沿用设置";
|
||||
return parts.length ? parts.join(" · ") : "沿用默认";
|
||||
}
|
||||
|
||||
function Node({ node, byParent, depth }: { node: AgentRunNode; byParent: Map<string, AgentRunNode[]>; depth: number }) {
|
||||
@@ -119,7 +119,7 @@ export function SubagentTree({ snapshot, className }: { snapshot: RunSnapshot; c
|
||||
</p>
|
||||
<RunLink id={parent.id}>{shortGoalTitle(parent.goal)}</RunLink>
|
||||
<p className="text-meta text-muted-foreground">
|
||||
第 {run.depth} 层子会话 · 继承父会话的发起人与授权上限 · 能力 {caps} 项(⊆ 父会话)· 划拨预算{" "}
|
||||
第 {run.depth} 层子会话 · 权限不超过父会话 · 可用能力 {caps} 项 · 预算{" "}
|
||||
{budgetText(run.budget as Record<string, number | undefined>)}
|
||||
{run.spawnReason ? ` · 理由:${run.spawnReason}` : ""}
|
||||
</p>
|
||||
@@ -129,7 +129,7 @@ export function SubagentTree({ snapshot, className }: { snapshot: RunSnapshot; c
|
||||
<div className="space-y-1">
|
||||
<p className="flex flex-wrap items-center gap-1.5 text-meta text-muted-foreground">
|
||||
<GitBranch aria-hidden className="size-3.5" />
|
||||
子会话 {children.length} 个{familyUsage ? <FamilyUsage usage={familyUsage} /> : null}
|
||||
{children.length} 个子会话{familyUsage ? <FamilyUsage usage={familyUsage} /> : null}
|
||||
</p>
|
||||
<ul className="space-y-0.5">
|
||||
{direct.map((c) => (
|
||||
@@ -143,5 +143,5 @@ export function SubagentTree({ snapshot, className }: { snapshot: RunSnapshot; c
|
||||
}
|
||||
|
||||
function FamilyUsage({ usage }: { usage: AgentFamilyUsage }) {
|
||||
return <span className="ml-auto tabular-nums">会话树合计 {usageLine(usage)}</span>;
|
||||
return <span className="ml-auto tabular-nums">合计 {usageLine(usage)}</span>;
|
||||
}
|
||||
@@ -29,7 +29,7 @@ export function authDenial(call: Pick<ToolCallView, "status" | "outputSummary">)
|
||||
/** 被服务端拒绝(白名单拦截或授权校验失败) */
|
||||
export const isDenied = (call: ToolCallView) => call.status === "denied" || authDenial(call) !== null;
|
||||
|
||||
/** 拒绝的一行说明:需要批准 / 本次没有开放 / 次数用完 */
|
||||
/** 拒绝的一行说明:需要批准 / 这次会话没开放 / 次数用完 */
|
||||
export function deniedReason(call: ToolCallView): { text: string; approvable: boolean } {
|
||||
const auth = authDenial(call);
|
||||
if (auth) {
|
||||
@@ -37,8 +37,8 @@ export function deniedReason(call: ToolCallView): { text: string; approvable: bo
|
||||
if (/自动化总开关|只读对账/.test(auth)) return { text: "自动执行已关闭", approvable: false };
|
||||
return { text: "需要你批准", approvable: true };
|
||||
}
|
||||
if (call.reason && /预算|次数/.test(call.reason)) return { text: "调用次数用完了", approvable: false };
|
||||
return { text: "本次没有开放", approvable: false };
|
||||
if (call.reason && /预算|次数/.test(call.reason)) return { text: "工具调用次数已用完", approvable: false };
|
||||
return { text: "这次会话没开放这个能力", approvable: false };
|
||||
}
|
||||
|
||||
// ── 进度行的人话计数 ──
|
||||
|
||||
@@ -30,7 +30,7 @@ const RULES: Rule[] = [
|
||||
},
|
||||
{
|
||||
match: /^\/console\/organize/,
|
||||
goals: ["识别需要处理的整理记录里的作品", "这次整理为什么有文件失败", "把失败的文件重新整理一遍"],
|
||||
goals: ["识别整理记录里没识别出的作品", "这次整理为什么有文件失败", "把失败的文件重新整理一遍"],
|
||||
},
|
||||
{
|
||||
match: /^\/console\/files/,
|
||||
|
||||
@@ -66,7 +66,7 @@ function ToolCallIoSheet({ call, onClose }: { call: ToolCallView; onClose: () =>
|
||||
<JsonView value={output.value ?? call.reason} maxHeight="18rem" tone={failed ? "destructive" : "neutral"} />
|
||||
</DetailSection>
|
||||
{input.truncated || output.truncated ? (
|
||||
<p className="text-caption text-muted-foreground">事件流只保存 500 字符以内的摘要,超出部分已截断。</p>
|
||||
<p className="text-caption text-muted-foreground">只保存前 500 个字符,其余已截断。</p>
|
||||
) : null}
|
||||
<DetailSection title="输入 schema">
|
||||
{tools.isPending ? (
|
||||
@@ -76,7 +76,7 @@ function ToolCallIoSheet({ call, onClose }: { call: ToolCallView; onClose: () =>
|
||||
) : tool ? (
|
||||
<JsonView value={tool.inputJsonSchema} maxHeight="20rem" />
|
||||
) : (
|
||||
<p className="text-caption text-muted-foreground">这个能力没有注册,没有 schema。</p>
|
||||
<p className="text-caption text-muted-foreground">这个能力未注册,没有 schema。</p>
|
||||
)}
|
||||
</DetailSection>
|
||||
{tool ? (
|
||||
|
||||
Reference in new issue
Block a user