feat(web): 智能体对话重做(AGUI-3)
- 会话流:用户消息 → 一行进度 → 结论正文 → 没做的事 → 一行实际结果。进度行把一段里的工具调用、思考和过程中的话 聚成一行(1 次也折叠),按能力 verb/noun 人话计数(「查了站点、2 个下载任务和媒体库 · 1 项没做 · 3 秒」), 展开每步一行;工具名、调用 ID、幂等键、输入输出进技术信息。去掉回合头、「模型说的」重复卡和系统核实大卡。 - agent.plan / agent.propose_options 不作为工具调用出现(旧事件回放同样跳过);被拒调用不出红卡, 回合末尾一行「没做:检测站点,需要你批准 [批准并重试]」(非计划类能力建一次性持续授权后重试)。 - 方案卡:每个方案一个按钮,点一下就选定;推荐项第一位、主按钮;按钮写服务端给的动作要点;风险折叠; 「都不要…」点开才出原因框;选完收成一行。审批卡只留摘要和批准 / 拒绝,计划 ID、hash、版本、到期进技术信息。 - 会话页去掉右侧栏和页头目标,步骤 / 子会话 / 模型 / 预算 / ID 进顶部概况条;输入区只有输入框和发送 / 停止, / 指令 /先给方案、/重跑(「先看方案再执行」开关原本只是新建时的本地状态,不需要迁移)。 - 抽屉去掉「当前 / 历史」Tab:头部「历史」弹层(最近 20 条)+「新对话」;上下文显示成名称标签,可去掉。 - 会话列表:搜索 + 全部 / 等你处理 / 失败;等你处理置顶,子会话收在父会话下,正常结束不加徽章。 - 入口页与空态给 3 个快捷目标。mock 加审查报告 3 个示例的演示数据;smoke 改测历史弹层。 - 组件测试:聚合、隐藏、拒绝一行、方案一键选定、历史置顶与子会话、指令。 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
2ab6d452dd
commit
6fcf82afb0
17 files changed
+2381
-1846
No files matched your search
@@ -20,7 +20,7 @@ import {
|
||||
agentRunEventFromRow,
|
||||
cursorPage,
|
||||
} from "@umt/contracts";
|
||||
import { MOCK_RUNS, mockCreateRun, mockDetail, mockEventsAfter } from "~/components/agent/mock.ts";
|
||||
import { MOCK_RUNS, mockChoose, mockCreateRun, mockDetail, mockEventsAfter } from "~/components/agent/mock.ts";
|
||||
import { TTL_SECOND } from "~/lib/api.ts";
|
||||
import { useSession } from "./auth.ts";
|
||||
import { apiMutate, apiParsed, cleanParams } from "./parse.ts";
|
||||
@@ -254,7 +254,7 @@ export function useChooseOption() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async (input: { runId: string; decisionId: string; optionId: string | null; reason?: string }) => {
|
||||
if (agentMockEnabled()) return mockDetail(input.runId).run;
|
||||
if (agentMockEnabled()) return mockChoose(input.runId, input.optionId).run;
|
||||
return apiMutate(`${AGENT_BASE}/:id/choice`, {
|
||||
pathParams: { id: input.runId },
|
||||
body: { decisionId: input.decisionId, optionId: input.optionId, reason: input.reason },
|
||||
@@ -264,6 +264,40 @@ export function useChooseOption() {
|
||||
});
|
||||
}
|
||||
|
||||
/** 需要按计划单次审批的能力:这类动作在会话里走审批卡,不另建持续授权 */
|
||||
const PLAN_APPROVAL_CAPS = new Set(["download.submit", "organize.execute", "policy.apply"]);
|
||||
|
||||
/**
|
||||
* 「批准并重试」(AGUI-3):被拒绝的调用由管理员批准一次后让智能体重试。
|
||||
* 非计划类能力建一条只覆盖这个能力、今天只能成功一次、1 小时后过期的持续授权(服务端核对,不靠模型转述);
|
||||
* 计划类能力只追加消息让智能体重试,重试时在步骤处照常出审批卡。
|
||||
*/
|
||||
export function useApproveAndRetry() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async (input: { runId: string; capabilityId: string; title: string }) => {
|
||||
const text = PLAN_APPROVAL_CAPS.has(input.capabilityId)
|
||||
? `重试「${input.title}」,需要审批时在步骤处等我批准。`
|
||||
: `已批准「${input.title}」一次,重试这一步。`;
|
||||
if (agentMockEnabled()) return mockDetail(input.runId, { append: text }).run;
|
||||
if (!PLAN_APPROVAL_CAPS.has(input.capabilityId))
|
||||
await apiMutate("/automation-grants", {
|
||||
body: {
|
||||
action: input.capabilityId,
|
||||
scope: { capabilities: [input.capabilityId] },
|
||||
budget: { maxInvocationsPerDay: 1 },
|
||||
reason: `智能体会话 ${input.runId.slice(0, 8)} 里批准并重试`,
|
||||
expiresAt: new Date(Date.now() + 3_600_000).toISOString(),
|
||||
},
|
||||
});
|
||||
return apiMutate(`${AGENT_BASE}/:id/messages`, { pathParams: { id: input.runId }, body: { text } }).then((d) =>
|
||||
AgentRun.parse(d),
|
||||
);
|
||||
},
|
||||
onSuccess: (_d, v) => qc.invalidateQueries({ queryKey: agentKeys.run(v.runId) }),
|
||||
});
|
||||
}
|
||||
|
||||
export const RUN_STATUS_LABEL: Record<AgentRunStatus, string> = {
|
||||
queued: "排队中",
|
||||
running: "运行中",
|
||||
|
||||
Reference in new issue
Block a user