Skip to content

Commit 9a43b94

Browse files
Share plan approval presentation logic
Generated-By: PostHog Code Task-Id: 40c57a59-b4e1-4760-8e56-ecd03e9c2f0f
1 parent a71ae3d commit 9a43b94

4 files changed

Lines changed: 63 additions & 49 deletions

File tree

apps/mobile/src/features/tasks/components/PlanApprovalCard.tsx

Lines changed: 6 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -44,40 +44,6 @@ function isRejectOption(
4444
return option.kind.startsWith("reject") || option.optionId.includes("reject");
4545
}
4646

47-
function extractTextContent(item: unknown): string | null {
48-
if (!item || typeof item !== "object") return null;
49-
50-
const record = item as Record<string, unknown>;
51-
if (typeof record.text === "string") {
52-
return record.text;
53-
}
54-
55-
if (!record.content || typeof record.content !== "object") {
56-
return null;
57-
}
58-
59-
const content = record.content as Record<string, unknown>;
60-
return typeof content.text === "string" ? content.text : null;
61-
}
62-
63-
function extractPlanText(
64-
permission?: CloudPendingPermissionRequest,
65-
): string | null {
66-
const rawPlan = permission?.toolCall.rawInput?.plan;
67-
if (typeof rawPlan === "string" && rawPlan.trim().length > 0) {
68-
return rawPlan;
69-
}
70-
71-
for (const item of permission?.toolCall.content ?? []) {
72-
const text = extractTextContent(item);
73-
if (text?.trim()) {
74-
return text;
75-
}
76-
}
77-
78-
return null;
79-
}
80-
8147
export function PlanApprovalCard({
8248
toolData,
8349
permission,
@@ -90,7 +56,10 @@ export function PlanApprovalCard({
9056
const [customInput, setCustomInput] = useState("");
9157

9258
const response = permission?.response;
93-
const planText = useMemo(() => extractPlanText(permission), [permission]);
59+
const planText = useMemo(
60+
() => (permission ? extractPlanText(permission.toolCall) : null),
61+
[permission],
62+
);
9463
const selectedOption = useMemo(
9564
() =>
9665
permission?.options.find(
@@ -270,3 +239,5 @@ export function PlanApprovalCard({
270239
</View>
271240
);
272241
}
242+
243+
import { extractPlanText } from "@posthog/core/sessions/planApprovalPresentation";
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { describe, expect, it } from "vitest";
2+
import { extractPlanText } from "./planApprovalPresentation";
3+
4+
describe("extractPlanText", () => {
5+
it.each([
6+
[{ rawInput: { plan: "Raw plan" } }, "Raw plan"],
7+
[{ content: [{ text: "Direct content" }] }, "Direct content"],
8+
[
9+
{
10+
content: [
11+
{ type: "content", content: { type: "text", text: "Nested" } },
12+
],
13+
},
14+
"Nested",
15+
],
16+
[{ rawInput: {}, content: [] }, null],
17+
])("extracts plan presentation from %o", (toolCall, expected) => {
18+
expect(extractPlanText(toolCall)).toBe(expected);
19+
});
20+
21+
it("prefers the canonical raw plan over rendered content", () => {
22+
expect(
23+
extractPlanText({
24+
rawInput: { plan: "Canonical" },
25+
content: [{ text: "Rendered" }],
26+
}),
27+
).toBe("Canonical");
28+
});
29+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
function extractTextContent(item: unknown): string | null {
2+
if (!item || typeof item !== "object") return null;
3+
const record = item as Record<string, unknown>;
4+
if (typeof record.text === "string") return record.text;
5+
6+
if (!record.content || typeof record.content !== "object") return null;
7+
const content = record.content as Record<string, unknown>;
8+
return typeof content.text === "string" ? content.text : null;
9+
}
10+
11+
export function extractPlanText(toolCall: {
12+
rawInput?: { plan?: unknown } | null;
13+
content?: readonly unknown[] | null;
14+
}): string | null {
15+
const rawPlan = toolCall.rawInput?.plan;
16+
if (typeof rawPlan === "string" && rawPlan.trim()) return rawPlan;
17+
18+
for (const item of toolCall.content ?? []) {
19+
const text = extractTextContent(item);
20+
if (text?.trim()) return text;
21+
}
22+
return null;
23+
}

packages/ui/src/features/sessions/components/session-update/PlanApprovalView.tsx

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { CaretDown, CaretRight, CheckCircle } from "@phosphor-icons/react";
2+
import { extractPlanText } from "@posthog/core/sessions/planApprovalPresentation";
23
import { Box, Flex, Text } from "@radix-ui/themes";
34
import { useMemo, useState } from "react";
45
import { PlanContent } from "../../../permissions/PlanContent";
@@ -32,20 +33,10 @@ export function PlanApprovalView({
3233
| undefined;
3334
const isHistoricalPlan = rawInput?.historical === true;
3435

35-
const planText = useMemo(() => {
36-
if (content?.length) {
37-
const textContent = content.find((c) => c.type === "content");
38-
if (textContent && "content" in textContent) {
39-
const inner = textContent.content as
40-
| { type?: string; text?: string }
41-
| undefined;
42-
if (inner?.type === "text" && inner.text) {
43-
return inner.text;
44-
}
45-
}
46-
}
47-
return rawInput?.plan ?? null;
48-
}, [content, rawInput?.plan]);
36+
const planText = useMemo(
37+
() => extractPlanText({ rawInput, content }),
38+
[content, rawInput],
39+
);
4940

5041
const wasNotApproved = isFailed || wasCancelled;
5142
const showResult = isHistoricalPlan || isComplete || wasNotApproved;

0 commit comments

Comments
 (0)