mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
feat(app): polish sidebar and tool call summaries
This commit is contained in:
@@ -779,7 +779,6 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
|
||||
isLastInSequence={layoutItem.isLastInToolSequence}
|
||||
onExpandedChange={setToolCallGroupExpanded}
|
||||
cwd={context.cwd}
|
||||
onOpenFilePath={handleToolCallOpenFile}
|
||||
>
|
||||
{expanded
|
||||
? group.run.calls.map((call, index) => (
|
||||
@@ -799,7 +798,6 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
|
||||
projectedToolCalls.groupsByHostId,
|
||||
context.cwd,
|
||||
expandedToolCallGroupIds,
|
||||
handleToolCallOpenFile,
|
||||
isMobile,
|
||||
renderSingleToolCallItem,
|
||||
setToolCallGroupExpanded,
|
||||
|
||||
@@ -519,6 +519,7 @@ function SidebarFooter({
|
||||
icon={Home}
|
||||
theme={theme}
|
||||
/>
|
||||
<SidebarHelpMenu />
|
||||
<FooterIconButton
|
||||
onPress={handleSettings}
|
||||
testID="sidebar-settings"
|
||||
@@ -527,7 +528,6 @@ function SidebarFooter({
|
||||
shortcutKeys={settingsKeys}
|
||||
theme={theme}
|
||||
/>
|
||||
<SidebarHelpMenu />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -21,21 +21,6 @@ export interface OverviewToolCallGroup {
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export type OverviewHeader = { kind: "latest"; call: ToolCallRun["latest"] } | { kind: "summary" };
|
||||
|
||||
export function resolveOverviewHeader(
|
||||
group: OverviewToolCallGroup,
|
||||
expanded: boolean,
|
||||
): OverviewHeader {
|
||||
if (group.run.calls.length === 1) {
|
||||
return { kind: "latest", call: group.run.latest };
|
||||
}
|
||||
if (expanded || group.run.isSealed) {
|
||||
return { kind: "summary" };
|
||||
}
|
||||
return { kind: "latest", call: group.run.latest };
|
||||
}
|
||||
|
||||
function isPaseoCall(name: string, normalizedName: string): boolean {
|
||||
return isPaseoToolName(name) || normalizedName.startsWith(DIRECT_PASEO_TOOL_PREFIX);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useToolCallSheet } from "@/components/tool-call-sheet";
|
||||
import { buildToolCallPresentation } from "@/tool-calls/presentation";
|
||||
import { resolveToolCallIcon } from "@/utils/tool-call-icon";
|
||||
import { describeToolCall } from "../grouping";
|
||||
import { resolveOverviewHeader, type OverviewSummary, type OverviewToolCallGroup } from "./model";
|
||||
import { type OverviewSummary, type OverviewToolCallGroup } from "./model";
|
||||
|
||||
interface OverviewGroupProps {
|
||||
group: OverviewToolCallGroup;
|
||||
@@ -18,7 +18,6 @@ interface OverviewGroupProps {
|
||||
isLastInSequence: boolean;
|
||||
onExpandedChange: (groupId: string, expanded: boolean) => void;
|
||||
cwd?: string;
|
||||
onOpenFilePath?: (filePath: string) => void;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
@@ -66,17 +65,14 @@ export const OverviewToolCallGroupView = memo(function OverviewToolCallGroupView
|
||||
isLastInSequence,
|
||||
onExpandedChange,
|
||||
cwd,
|
||||
onOpenFilePath,
|
||||
children,
|
||||
}: OverviewGroupProps) {
|
||||
const { t } = useTranslation();
|
||||
const { openToolCall } = useToolCallSheet();
|
||||
const scrollRef = useRef<ScrollView>(null);
|
||||
const aggregateSummary = useOverviewSummary(group.summary);
|
||||
const header = resolveOverviewHeader(group, expanded);
|
||||
const latestCall = header.kind === "latest" ? header.call : group.run.latest;
|
||||
const latest = useMemo(() => {
|
||||
const descriptor = describeToolCall(latestCall);
|
||||
const descriptor = describeToolCall(group.run.latest);
|
||||
return {
|
||||
detail: descriptor.detail,
|
||||
presentation: buildToolCallPresentation({
|
||||
@@ -89,16 +85,8 @@ export const OverviewToolCallGroupView = memo(function OverviewToolCallGroupView
|
||||
resolveIcon: resolveToolCallIcon,
|
||||
}),
|
||||
};
|
||||
}, [cwd, latestCall]);
|
||||
const showsLatest = header.kind === "latest";
|
||||
}, [cwd, group.run.latest]);
|
||||
const opensSingleCallSheet = isCompact && group.run.calls.length === 1;
|
||||
const openLatestFile = useMemo(() => {
|
||||
const path = latest.presentation.openFilePath;
|
||||
if (!showsLatest || !path || !onOpenFilePath) {
|
||||
return undefined;
|
||||
}
|
||||
return () => onOpenFilePath(path);
|
||||
}, [showsLatest, latest.presentation.openFilePath, onOpenFilePath]);
|
||||
const failedSummary =
|
||||
group.failedCount > 0 ? t("toolCallGroup.failed", { count: group.failedCount }) : undefined;
|
||||
const scrollToLatest = useCallback(() => {
|
||||
@@ -147,15 +135,14 @@ export const OverviewToolCallGroupView = memo(function OverviewToolCallGroupView
|
||||
return (
|
||||
<ExpandableBadge
|
||||
testID="tool-call-group"
|
||||
label={showsLatest ? latest.presentation.displayName : aggregateSummary}
|
||||
secondaryLabel={showsLatest ? latest.presentation.summary : failedSummary}
|
||||
icon={showsLatest ? latest.presentation.icon : Wrench}
|
||||
label={aggregateSummary}
|
||||
secondaryLabel={failedSummary}
|
||||
icon={Wrench}
|
||||
isLoading={group.isLoading}
|
||||
isError={group.failedCount > 0}
|
||||
isExpanded={opensSingleCallSheet ? false : expanded}
|
||||
isLastInSequence={isLastInSequence}
|
||||
onToggle={canExpand ? toggle : undefined}
|
||||
onOpenFile={openLatestFile}
|
||||
renderDetails={canExpand && !opensSingleCallSheet ? renderDetails : undefined}
|
||||
borderlessWhenExpanded
|
||||
/>
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
type PreparedToolCallHistory,
|
||||
type ToolCallDetailLevel,
|
||||
} from "./projection";
|
||||
import { resolveOverviewHeader } from "./overview/model";
|
||||
|
||||
type AssistantMessageItem = Extract<StreamItem, { kind: "assistant_message" }>;
|
||||
|
||||
@@ -111,25 +110,6 @@ describe("tool call detail-level projection", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("shows the latest call while collapsed and the summary while expanded", () => {
|
||||
const calls = [
|
||||
toolCall("1", { type: "shell", command: "one" }),
|
||||
toolCall("2", { type: "read", filePath: "/repo/a.ts" }, { status: "running" }),
|
||||
];
|
||||
const result = project({
|
||||
level: "overview",
|
||||
head: calls,
|
||||
isTurnActive: true,
|
||||
});
|
||||
const group = result.groupsByHostId.get("1");
|
||||
if (!group) {
|
||||
throw new Error("Expected an overview group");
|
||||
}
|
||||
|
||||
expect(resolveOverviewHeader(group, false)).toEqual({ kind: "latest", call: calls[1] });
|
||||
expect(resolveOverviewHeader(group, true)).toEqual({ kind: "summary" });
|
||||
});
|
||||
|
||||
it("keeps a parallel group loading while any call is still running", () => {
|
||||
const calls = [
|
||||
toolCall("1", { type: "shell", command: "slow" }, { status: "running" }),
|
||||
@@ -140,16 +120,18 @@ describe("tool call detail-level projection", () => {
|
||||
expect(result.groupsByHostId.get("1")?.isLoading).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps a one-call run presented as its tool call", () => {
|
||||
const call = toolCall("1", { type: "shell", command: "one" });
|
||||
const result = project({ level: "overview", head: [call] });
|
||||
it("builds a loading aggregate for a one-call run", () => {
|
||||
const call = toolCall("1", { type: "shell", command: "one" }, { status: "running" });
|
||||
const result = project({ level: "overview", head: [call], isTurnActive: true });
|
||||
const group = result.groupsByHostId.get(call.id);
|
||||
if (!group) {
|
||||
throw new Error("Expected an overview group");
|
||||
}
|
||||
|
||||
expect(resolveOverviewHeader(group, false)).toEqual({ kind: "latest", call });
|
||||
expect(resolveOverviewHeader(group, true)).toEqual({ kind: "latest", call });
|
||||
expect(group).toMatchObject({
|
||||
isLoading: true,
|
||||
summary: { commandCount: 1 },
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps an active overview group on its latest call until a visible boundary arrives", () => {
|
||||
@@ -172,11 +154,6 @@ describe("tool call detail-level projection", () => {
|
||||
mode: "overview",
|
||||
run: { id: "1", latest: calls[3], isSealed: false },
|
||||
});
|
||||
expect(resolveOverviewHeader(activeGroup!, false)).toEqual({
|
||||
kind: "latest",
|
||||
call: calls[3],
|
||||
});
|
||||
|
||||
const boundary = assistant("answer");
|
||||
const sealed = project({
|
||||
level: "overview",
|
||||
@@ -189,12 +166,9 @@ describe("tool call detail-level projection", () => {
|
||||
run: { latest: calls[3], isSealed: true },
|
||||
summary: { editedFileCount: 1, readFileCount: 2, commandCount: 1 },
|
||||
});
|
||||
expect(resolveOverviewHeader(sealed.groupsByHostId.get("1")!, false)).toEqual({
|
||||
kind: "summary",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps a running overview header live before the agent lifecycle catches up", () => {
|
||||
it("keeps a running overview group live before the agent lifecycle catches up", () => {
|
||||
const calls = ["1", "2", "3", "4"].map((id) =>
|
||||
toolCall(id, { type: "shell", command: id }, { status: "running" }),
|
||||
);
|
||||
@@ -207,10 +181,8 @@ describe("tool call detail-level projection", () => {
|
||||
|
||||
expect(result.groupsByHostId.get("1")).toMatchObject({
|
||||
run: { latest: calls[3], isSealed: false },
|
||||
});
|
||||
expect(resolveOverviewHeader(result.groupsByHostId.get("1")!, false)).toEqual({
|
||||
kind: "latest",
|
||||
call: calls[3],
|
||||
isLoading: true,
|
||||
summary: { commandCount: 4 },
|
||||
});
|
||||
});
|
||||
|
||||
@@ -239,22 +211,11 @@ describe("tool call detail-level projection", () => {
|
||||
});
|
||||
|
||||
expect(betweenCalls.groupsByHostId.get("1")?.run.isSealed).toBe(false);
|
||||
expect(resolveOverviewHeader(betweenCalls.groupsByHostId.get("1")!, false)).toEqual({
|
||||
kind: "latest",
|
||||
call: calls[3],
|
||||
});
|
||||
expect(continued.groupsByHostId.get("1")?.run).toMatchObject({
|
||||
latest: nextCall,
|
||||
isSealed: false,
|
||||
});
|
||||
expect(resolveOverviewHeader(continued.groupsByHostId.get("1")!, false)).toEqual({
|
||||
kind: "latest",
|
||||
call: nextCall,
|
||||
});
|
||||
expect(ended.groupsByHostId.get("1")?.run.isSealed).toBe(true);
|
||||
expect(resolveOverviewHeader(ended.groupsByHostId.get("1")!, false)).toEqual({
|
||||
kind: "summary",
|
||||
});
|
||||
});
|
||||
|
||||
it("builds overview summaries without category-specific presentation data", () => {
|
||||
|
||||
Reference in New Issue
Block a user