Skip to content

Commit 6dda1c0

Browse files
committed
Make setup flow complete
1 parent 22348e2 commit 6dda1c0

13 files changed

Lines changed: 656 additions & 133 deletions

File tree

apps/twig/src/api/posthogClient.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,4 +405,55 @@ export class PostHogAPIClient {
405405
count: data.count ?? data.results?.length ?? data?.length ?? 0,
406406
};
407407
}
408+
409+
/**
410+
* Run a HogQL query against the PostHog query API
411+
*/
412+
async runQuery<T = unknown>(query: {
413+
kind: string;
414+
query: string;
415+
}): Promise<T> {
416+
const teamId = await this.getTeamId();
417+
const url = new URL(`${this.api.baseUrl}/api/projects/${teamId}/query/`);
418+
const response = await this.api.fetcher.fetch({
419+
method: "post",
420+
url,
421+
path: `/api/projects/${teamId}/query/`,
422+
overrides: {
423+
body: JSON.stringify(query),
424+
},
425+
});
426+
427+
if (!response.ok) {
428+
throw new Error(`Failed to run query: ${response.statusText}`);
429+
}
430+
431+
return await response.json();
432+
}
433+
434+
/**
435+
* Update the current team/project settings
436+
*/
437+
async updateTeam(updates: {
438+
proactive_tasks_enabled?: boolean;
439+
session_recording_opt_in?: boolean;
440+
autocapture_exceptions_opt_in?: boolean;
441+
}): Promise<Schemas.Team> {
442+
const teamId = await this.getTeamId();
443+
const url = new URL(`${this.api.baseUrl}/api/projects/${teamId}/`);
444+
const response = await this.api.fetcher.fetch({
445+
method: "patch",
446+
url,
447+
path: `/api/projects/${teamId}/`,
448+
overrides: {
449+
body: JSON.stringify(updates),
450+
},
451+
});
452+
453+
if (!response.ok) {
454+
throw new Error(`Failed to update team: ${response.statusText}`);
455+
}
456+
457+
return await response.json();
458+
}
408459
}

apps/twig/src/renderer/components/MainLayout.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { HeaderRow } from "@components/HeaderRow";
33
import { KeyboardShortcutsSheet } from "@components/KeyboardShortcutsSheet";
44
import { StatusBar } from "@components/StatusBar";
55
import { UpdatePrompt } from "@components/UpdatePrompt";
6+
import { AutonomyOnboarding } from "@features/autonomy/components/AutonomyOnboarding";
7+
import { AutonomyTasksView } from "@features/autonomy/components/AutonomyTasksView";
68
import { CommandMenu } from "@features/command/components/CommandMenu";
79
import { RightSidebar, RightSidebarContent } from "@features/right-sidebar";
810
import { FolderSettingsView } from "@features/settings/components/FolderSettingsView";
@@ -66,6 +68,10 @@ export function MainLayout() {
6668
{view.type === "settings" && <SettingsView />}
6769

6870
{view.type === "folder-settings" && <FolderSettingsView />}
71+
72+
{view.type === "autonomy-tasks" && <AutonomyTasksView />}
73+
74+
{view.type === "autonomy-onboarding" && <AutonomyOnboarding />}
6975
</Box>
7076

7177
{view.type === "task-detail" && view.data && (
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
import { useAuthStore } from "@features/auth/stores/authStore";
2+
import {
3+
ArrowLeftIcon,
4+
CircleNotchIcon,
5+
SparkleIcon,
6+
WarningCircleIcon,
7+
} from "@phosphor-icons/react";
8+
import {
9+
Box,
10+
Button,
11+
Callout,
12+
Flex,
13+
Heading,
14+
Link,
15+
Text,
16+
} from "@radix-ui/themes";
17+
import { get } from "@renderer/di/container";
18+
import { RENDERER_TOKENS } from "@renderer/di/tokens";
19+
import type { TaskService } from "@renderer/services/task/service";
20+
import { useNavigationStore } from "@renderer/stores/navigationStore";
21+
import { useTaskDirectoryStore } from "@renderer/stores/taskDirectoryStore";
22+
import { useQueryClient } from "@tanstack/react-query";
23+
import { useCallback, useState } from "react";
24+
import { SETUP_TASK_PROMPT } from "@/renderer/features/autonomy/utils/createPostHogSetupTask";
25+
26+
export function AutonomyOnboarding() {
27+
const client = useAuthStore((state) => state.client);
28+
const projectId = useAuthStore((state) => state.projectId);
29+
const queryClient = useQueryClient();
30+
const { navigateToTask, navigateToTaskInput } = useNavigationStore();
31+
const lastUsedDirectory = useTaskDirectoryStore(
32+
(state) => state.lastUsedDirectory,
33+
);
34+
35+
const [isCreatingTask, setIsCreatingTask] = useState(false);
36+
const [taskError, setTaskError] = useState<string | null>(null);
37+
38+
const handleSetupAutonomy = useCallback(async () => {
39+
if (!client || !lastUsedDirectory) return;
40+
41+
setIsCreatingTask(true);
42+
setTaskError(null);
43+
44+
try {
45+
await client.updateTeam({ proactive_tasks_enabled: true });
46+
47+
// Force refetch of project query to get updated team data
48+
if (projectId) {
49+
await queryClient.refetchQueries({
50+
queryKey: ["project", projectId],
51+
});
52+
}
53+
54+
const taskService = get<TaskService>(RENDERER_TOKENS.TaskService);
55+
const result = await taskService.createTask({
56+
content: SETUP_TASK_PROMPT,
57+
repoPath: lastUsedDirectory,
58+
workspaceMode: "worktree",
59+
});
60+
61+
if (result.success) {
62+
navigateToTask(result.data.task);
63+
} else {
64+
setTaskError(result.error);
65+
setIsCreatingTask(false);
66+
}
67+
} catch (err) {
68+
setTaskError(
69+
err instanceof Error ? err.message : "Failed to create setup task",
70+
);
71+
setIsCreatingTask(false);
72+
}
73+
}, [client, projectId, lastUsedDirectory, navigateToTask, queryClient]);
74+
75+
const handleBack = useCallback(() => {
76+
setTaskError(null);
77+
navigateToTaskInput();
78+
}, [navigateToTaskInput]);
79+
80+
const repoName = lastUsedDirectory?.split("/").pop() ?? null;
81+
const isDisabled = !lastUsedDirectory || isCreatingTask;
82+
83+
return (
84+
<Flex
85+
direction="column"
86+
align="center"
87+
justify="center"
88+
className="h-full w-full p-8"
89+
>
90+
<Box className="w-full max-w-md">
91+
<Box className="space-y-4">
92+
<Flex align="center" gap="2">
93+
<Heading size="5">Welcome to Autonomy</Heading>
94+
<SparkleIcon size={28} className="text-accent-9" />
95+
</Flex>
96+
97+
<Flex direction="column" gap="2" className="my-4">
98+
<Callout.Root size="1" color="gray" className="-ml-2 mr-2">
99+
<Callout.Text>
100+
<strong>
101+
Twig Autonomy hands you ready-to-run fixes for real user
102+
problems.
103+
</strong>{" "}
104+
Just approve or reject tasks - each one comes with hard evidence
105+
and impact numbers, so you know exactly what you're addressing
106+
and why.
107+
</Callout.Text>
108+
</Callout.Root>
109+
110+
<Text size="1" color="gray" weight="bold" className="ml-2">
111+
But how does it know what needs fixing or improving?
112+
</Text>
113+
114+
<Callout.Root size="1" color="gray" className="-mr-2 ml-2">
115+
<Callout.Text>
116+
<strong>
117+
It spots patterns in usage that would take you hours to find.
118+
</strong>{" "}
119+
Confusing flows, silently broken buttons, full-on crashes -
120+
Autonomy connects the dots to surface what matters.
121+
</Callout.Text>
122+
</Callout.Root>
123+
124+
<Text size="1" color="gray" weight="bold" className="-ml-2">
125+
Where is it getting those insights from?
126+
</Text>
127+
128+
<Callout.Root size="1" color="gray" className="-ml-2 mr-2">
129+
<Callout.Text>
130+
<strong>It watches every session and analyzes it.</strong> Each
131+
user interaction, error, and visual glitch gets dissected
132+
automatically. No more hunting through logs or waiting for bug
133+
reports.
134+
</Callout.Text>
135+
</Callout.Root>
136+
137+
<Text size="1" color="gray" weight="bold" className="ml-2">
138+
What makes this possible?
139+
</Text>
140+
141+
<Callout.Root size="1" color="gray" className="-mr-2 ml-2">
142+
<Callout.Text>
143+
<strong>
144+
First, we ensure a full tracking setup with{" "}
145+
<Link
146+
href="https://posthog.com/"
147+
color="gray"
148+
underline="always"
149+
>
150+
PostHog
151+
</Link>
152+
.
153+
</strong>{" "}
154+
Session recordings, analytics, and error tracking - this is what
155+
we'll set up now (if not in place yet). Once the data's flowing,
156+
Autonomy begins its work.
157+
</Callout.Text>
158+
</Callout.Root>
159+
</Flex>
160+
161+
{taskError && (
162+
<Callout.Root color="red" size="1">
163+
<Callout.Icon>
164+
<WarningCircleIcon />
165+
</Callout.Icon>
166+
<Callout.Text>{taskError}</Callout.Text>
167+
</Callout.Root>
168+
)}
169+
170+
<Flex gap="3" justify="end">
171+
<Button variant="soft" color="gray" onClick={handleBack}>
172+
<ArrowLeftIcon size={16} />
173+
Back
174+
</Button>
175+
<Button
176+
onClick={handleSetupAutonomy}
177+
disabled={isDisabled}
178+
title={
179+
!lastUsedDirectory ? "Select a repository first" : undefined
180+
}
181+
>
182+
{isCreatingTask ? (
183+
<CircleNotchIcon size={16} className="animate-spin" />
184+
) : (
185+
<SparkleIcon size={16} />
186+
)}
187+
{repoName
188+
? `Set up Autonomy for ${repoName} (8 min)`
189+
: "Set up Autonomy (8 min)"}
190+
</Button>
191+
</Flex>
192+
</Box>
193+
</Box>
194+
</Flex>
195+
);
196+
}

0 commit comments

Comments
 (0)