diff --git a/apps/mobile/src/features/chat/components/AgentMessage.tsx b/apps/mobile/src/features/chat/components/AgentMessage.tsx
index e6fa274cb6..e767f2f43a 100644
--- a/apps/mobile/src/features/chat/components/AgentMessage.tsx
+++ b/apps/mobile/src/features/chat/components/AgentMessage.tsx
@@ -1,10 +1,10 @@
+import { pickThinkingActivity } from "@posthog/core/sessions/thinkingActivities";
import { Brain } from "phosphor-react-native";
import { useState } from "react";
import { Pressable, Text, View } from "react-native";
import { formatRelativeTime } from "@/lib/format";
import { useThemeColors } from "@/lib/theme";
import { usePeriodicRerender } from "../hooks/usePeriodicRerender";
-import { getRandomThinkingMessage } from "../utils/thinkingMessages";
import { CopyButton } from "./CopyButton";
import { MarkdownText } from "./MarkdownText";
import { ToolMessage } from "./ToolMessage";
@@ -110,7 +110,7 @@ export function AgentMessage({
{isLoading && !content && !thinkingText && (
- {getRandomThinkingMessage()}
+ {pickThinkingActivity(Math.random())}...
)}
diff --git a/apps/mobile/src/features/chat/components/ToolMessage.tsx b/apps/mobile/src/features/chat/components/ToolMessage.tsx
index 3859504777..edf249162b 100644
--- a/apps/mobile/src/features/chat/components/ToolMessage.tsx
+++ b/apps/mobile/src/features/chat/components/ToolMessage.tsx
@@ -1,3 +1,8 @@
+import {
+ formatPosthogExecBody,
+ getPostHogExecDisplay,
+ isPostHogExecTool,
+} from "@posthog/core/sessions/posthogExecDisplay";
import { useRouter } from "expo-router";
import {
ArrowsClockwise,
@@ -22,11 +27,6 @@ import {
TouchableOpacity,
View,
} from "react-native";
-import {
- formatPosthogExecBody,
- getPostHogExecDisplay,
- isPostHogExecTool,
-} from "@/features/chat/utils/posthogExecDisplay";
import { McpAppHost } from "@/features/mcp/components/McpAppHost";
import { isMcpToolName } from "@/features/mcp/utils/mcpToolName";
import {
diff --git a/apps/mobile/src/features/chat/utils/posthogExecDisplay.test.ts b/apps/mobile/src/features/chat/utils/posthogExecDisplay.test.ts
index c2a81f3e0f..ca08b80c33 100644
--- a/apps/mobile/src/features/chat/utils/posthogExecDisplay.test.ts
+++ b/apps/mobile/src/features/chat/utils/posthogExecDisplay.test.ts
@@ -1,9 +1,9 @@
-import { describe, expect, it } from "vitest";
import {
formatPosthogExecBody,
getPostHogExecDisplay,
isPostHogExecTool,
-} from "./posthogExecDisplay";
+} from "@posthog/core/sessions/posthogExecDisplay";
+import { describe, expect, it } from "vitest";
describe("isPostHogExecTool", () => {
it("matches the bare posthog exec tool", () => {
diff --git a/apps/mobile/src/features/chat/utils/posthogExecDisplay.ts b/apps/mobile/src/features/chat/utils/posthogExecDisplay.ts
deleted file mode 100644
index d46e759f25..0000000000
--- a/apps/mobile/src/features/chat/utils/posthogExecDisplay.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-/**
- * Mirrors the desktop PostHog MCP exec display logic so mobile unwraps the
- * dispatched sub-tool instead of showing the raw `exec` transport wrapper.
- *
- * Supported verbs:
- * tools
- * search
- * info
- * schema [field_path]
- * call [--json]
- */
-
-const POSTHOG_EXEC_TOOL_RE = /^mcp__(?:plugin_)?posthog(?:_[^_]+)*__exec$/;
-
-const POSTHOG_VERB_RE =
- /^\s*(tools|search|info|schema|call)(?:\s+([\s\S]*))?\s*$/;
-const POSTHOG_CALL_BODY_RE = /^(?:--json\s+)?([a-zA-Z0-9_-]+)\s*([\s\S]*)$/;
-const POSTHOG_TOOL_NAME_RE = /^([a-zA-Z0-9_-]+)\s*([\s\S]*)$/;
-
-export interface PostHogExecDisplay {
- label: string;
- input?: string;
-}
-
-export function isPostHogExecTool(toolName: string): boolean {
- return POSTHOG_EXEC_TOOL_RE.test(toolName);
-}
-
-export function getPostHogExecDisplay(
- toolInput: unknown,
-): PostHogExecDisplay | null {
- if (!toolInput || typeof toolInput !== "object") return null;
- const obj = toolInput as { command?: unknown; input?: unknown };
-
- if (typeof obj.command !== "string") return null;
- const verbMatch = obj.command.match(POSTHOG_VERB_RE);
- if (!verbMatch) return null;
-
- const verb = verbMatch[1] as "tools" | "search" | "info" | "schema" | "call";
- const rest = (verbMatch[2] ?? "").trim();
- const explicitInput = readExplicitInput(obj.input);
-
- switch (verb) {
- case "tools":
- return { label: "List tools", input: undefined };
-
- case "search":
- return {
- label: "Search tools",
- input: explicitInput ?? (rest.length > 0 ? rest : undefined),
- };
-
- case "info":
- return rest.length > 0
- ? { label: `Read ${rest}`, input: undefined }
- : { label: "Read tool", input: undefined };
-
- case "schema": {
- const match = rest.match(POSTHOG_TOOL_NAME_RE);
- if (!match) return { label: "Inspect schema", input: undefined };
- const subTool = match[1];
- const fieldPath = (match[2] ?? "").trim();
- const path =
- explicitInput ?? (fieldPath.length > 0 ? fieldPath : undefined);
- return {
- label: path
- ? `Inspect ${subTool}.${path}`
- : `Inspect ${subTool} fields`,
- input: undefined,
- };
- }
-
- case "call": {
- const match = rest.match(POSTHOG_CALL_BODY_RE);
- if (!match) return null;
- const subTool = match[1];
- const args = (match[2] ?? "").trim();
- return {
- label: subTool,
- input: explicitInput ?? (args.length > 0 ? args : undefined),
- };
- }
- }
-}
-
-function readExplicitInput(value: unknown): string | undefined {
- if (value === undefined || value === null) return undefined;
- if (typeof value === "string") return value.trim() || undefined;
- try {
- return JSON.stringify(value);
- } catch {
- return undefined;
- }
-}
-
-export function formatPosthogExecBody(
- input: string | undefined,
-): string | undefined {
- if (!input) return undefined;
- try {
- const parsed = JSON.parse(input);
- if (parsed && typeof parsed === "object") {
- return JSON.stringify(parsed, null, 2);
- }
- } catch {
- // Not JSON; show the raw input.
- }
- return input;
-}
diff --git a/apps/mobile/src/features/chat/utils/thinkingMessages.test.ts b/apps/mobile/src/features/chat/utils/thinkingMessages.test.ts
deleted file mode 100644
index de528d1560..0000000000
--- a/apps/mobile/src/features/chat/utils/thinkingMessages.test.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { describe, expect, it, vi } from "vitest";
-import {
- getRandomThinkingActivity,
- getRandomThinkingMessage,
- THINKING_MESSAGES,
-} from "./thinkingMessages";
-
-describe("thinkingMessages", () => {
- it("includes the whimsical cloud-run loading messages from desktop", () => {
- expect(THINKING_MESSAGES).toContain("Kerfuffling");
- expect(THINKING_MESSAGES).toContain("Flibbertigibbeting");
- expect(THINKING_MESSAGES).toContain("Discombobulating");
- });
-
- it("returns a bare activity label and a message variant with ellipsis", () => {
- const randomSpy = vi.spyOn(Math, "random").mockReturnValue(0);
-
- expect(getRandomThinkingActivity()).toBe("Booping");
- expect(getRandomThinkingMessage()).toBe("Booping...");
-
- randomSpy.mockRestore();
- });
-});
diff --git a/apps/mobile/src/features/tasks/components/TaskSessionView.test.tsx b/apps/mobile/src/features/tasks/components/TaskSessionView.test.tsx
index 5bab080e15..e55c5f0f72 100644
--- a/apps/mobile/src/features/tasks/components/TaskSessionView.test.tsx
+++ b/apps/mobile/src/features/tasks/components/TaskSessionView.test.tsx
@@ -24,8 +24,8 @@ vi.mock("@/features/chat", () => ({
deriveToolKind: () => "other",
}));
-vi.mock("@/features/chat/utils/thinkingMessages", () => ({
- getRandomThinkingActivity: () => "Thinking",
+vi.mock("@posthog/core/sessions/thinkingActivities", () => ({
+ pickThinkingActivity: () => "Thinking",
}));
vi.mock("@/lib/theme", () => ({
diff --git a/apps/mobile/src/features/tasks/components/TaskSessionView.tsx b/apps/mobile/src/features/tasks/components/TaskSessionView.tsx
index f30b4aec66..75a06938b0 100644
--- a/apps/mobile/src/features/tasks/components/TaskSessionView.tsx
+++ b/apps/mobile/src/features/tasks/components/TaskSessionView.tsx
@@ -1,3 +1,4 @@
+import { pickThinkingActivity } from "@posthog/core/sessions/thinkingActivities";
import {
ArrowDown,
Brain,
@@ -20,7 +21,6 @@ import {
ToolMessage,
type ToolStatus,
} from "@/features/chat";
-import { getRandomThinkingActivity } from "@/features/chat/utils/thinkingMessages";
import { useThemeColors } from "@/lib/theme";
import type {
CloudPendingPermissionRequest,
@@ -734,7 +734,9 @@ function useElapsedTimer() {
function ThinkingIndicator() {
const [dots, setDots] = useState(1);
- const [activity, setActivity] = useState(getRandomThinkingActivity);
+ const [activity, setActivity] = useState(() =>
+ pickThinkingActivity(Math.random()),
+ );
const elapsed = useElapsedTimer();
const themeColors = useThemeColors();
@@ -747,7 +749,7 @@ function ThinkingIndicator() {
useEffect(() => {
const interval = setInterval(() => {
- setActivity(getRandomThinkingActivity());
+ setActivity(pickThinkingActivity(Math.random()));
}, 2000);
return () => clearInterval(interval);
}, []);
diff --git a/packages/core/src/sessions/posthogExecDisplay.ts b/packages/core/src/sessions/posthogExecDisplay.ts
new file mode 100644
index 0000000000..5b3029a535
--- /dev/null
+++ b/packages/core/src/sessions/posthogExecDisplay.ts
@@ -0,0 +1,85 @@
+import { parseMcpToolName } from "@posthog/shared";
+
+const POSTHOG_SERVER_RE = /^(?:plugin_)?posthog(?:_[^_]+)*$/;
+const POSTHOG_VERB_RE =
+ /^\s*(tools|search|info|schema|call)(?:\s+([\s\S]*))?\s*$/;
+const POSTHOG_CALL_BODY_RE = /^(?:--json\s+)?([a-zA-Z0-9_-]+)\s*([\s\S]*)$/;
+const POSTHOG_TOOL_NAME_RE = /^([a-zA-Z0-9_-]+)\s*([\s\S]*)$/;
+
+export interface PostHogExecDisplay {
+ label: string;
+ input?: string;
+}
+
+export function isPostHogExecTool(toolName: string): boolean {
+ const mcp = parseMcpToolName(toolName);
+ return !!mcp && mcp.tool === "exec" && POSTHOG_SERVER_RE.test(mcp.server);
+}
+
+export function getPostHogExecDisplay(
+ toolInput: unknown,
+): PostHogExecDisplay | null {
+ if (!toolInput || typeof toolInput !== "object") return null;
+ const input = toolInput as { command?: unknown; input?: unknown };
+ if (typeof input.command !== "string") return null;
+ const match = input.command.match(POSTHOG_VERB_RE);
+ if (!match) return null;
+ const verb = match[1] as "tools" | "search" | "info" | "schema" | "call";
+ const rest = (match[2] ?? "").trim();
+ const explicitInput = readExplicitInput(input.input);
+
+ switch (verb) {
+ case "tools":
+ return { label: "List tools", input: undefined };
+ case "search":
+ return {
+ label: "Search tools",
+ input: explicitInput ?? (rest || undefined),
+ };
+ case "info":
+ return { label: rest ? `Read ${rest}` : "Read tool", input: undefined };
+ case "schema": {
+ const schema = rest.match(POSTHOG_TOOL_NAME_RE);
+ if (!schema) return { label: "Inspect schema", input: undefined };
+ const path = explicitInput ?? ((schema[2] ?? "").trim() || undefined);
+ return {
+ label: path
+ ? `Inspect ${schema[1]}.${path}`
+ : `Inspect ${schema[1]} fields`,
+ input: undefined,
+ };
+ }
+ case "call": {
+ const call = rest.match(POSTHOG_CALL_BODY_RE);
+ if (!call) return null;
+ return {
+ label: call[1],
+ input: explicitInput ?? ((call[2] ?? "").trim() || undefined),
+ };
+ }
+ }
+}
+
+function readExplicitInput(value: unknown): string | undefined {
+ if (value === undefined || value === null) return undefined;
+ if (typeof value === "string") return value.trim() || undefined;
+ try {
+ return JSON.stringify(value);
+ } catch {
+ return undefined;
+ }
+}
+
+export function formatPosthogExecBody(
+ input: string | undefined,
+): string | undefined {
+ if (!input) return undefined;
+ try {
+ const parsed = JSON.parse(input);
+ if (parsed && typeof parsed === "object")
+ return JSON.stringify(parsed, null, 2);
+ } catch {
+ return input;
+ }
+ return input;
+}
diff --git a/packages/core/src/sessions/thinkingActivities.test.ts b/packages/core/src/sessions/thinkingActivities.test.ts
new file mode 100644
index 0000000000..1b1ca94aa6
--- /dev/null
+++ b/packages/core/src/sessions/thinkingActivities.test.ts
@@ -0,0 +1,17 @@
+import { describe, expect, it } from "vitest";
+import {
+ pickNextThinkingActivity,
+ pickThinkingActivity,
+ THINKING_ACTIVITIES,
+} from "./thinkingActivities";
+
+describe("thinking activities", () => {
+ it("selects from a bounded random value", () => {
+ expect(pickThinkingActivity(0)).toBe("Booping");
+ expect(pickThinkingActivity(1)).toBe(THINKING_ACTIVITIES.at(-1));
+ });
+
+ it("always advances when the random pick matches the current activity", () => {
+ expect(pickNextThinkingActivity("Booping", 0)).toBe("Crunching");
+ });
+});
diff --git a/apps/mobile/src/features/chat/utils/thinkingMessages.ts b/packages/core/src/sessions/thinkingActivities.ts
similarity index 64%
rename from apps/mobile/src/features/chat/utils/thinkingMessages.ts
rename to packages/core/src/sessions/thinkingActivities.ts
index 35bd41b351..0046775ffc 100644
--- a/apps/mobile/src/features/chat/utils/thinkingMessages.ts
+++ b/packages/core/src/sessions/thinkingActivities.ts
@@ -1,7 +1,4 @@
-// Random thinking messages displayed while AI is generating
-// Based on posthog/frontend/src/scenes/max/utils/thinkingMessages.ts
-
-export const THINKING_MESSAGES = [
+export const THINKING_ACTIVITIES = [
"Booping",
"Crunching",
"Digging",
@@ -86,13 +83,21 @@ export const THINKING_MESSAGES = [
"Puttering",
"Whiffling",
"Thinking",
-];
+] as const;
-export function getRandomThinkingActivity(): string {
- const randomIndex = Math.floor(Math.random() * THINKING_MESSAGES.length);
- return THINKING_MESSAGES[randomIndex];
+export function pickThinkingActivity(randomValue: number): string {
+ const bounded = Math.max(0, Math.min(randomValue, 0.999999999999));
+ return THINKING_ACTIVITIES[Math.floor(bounded * THINKING_ACTIVITIES.length)];
}
-export function getRandomThinkingMessage(): string {
- return `${getRandomThinkingActivity()}...`;
+export function pickNextThinkingActivity(
+ current: string,
+ randomValue: number,
+): string {
+ const picked = pickThinkingActivity(randomValue);
+ if (picked !== current || THINKING_ACTIVITIES.length <= 1) return picked;
+ const index = THINKING_ACTIVITIES.indexOf(
+ current as (typeof THINKING_ACTIVITIES)[number],
+ );
+ return THINKING_ACTIVITIES[(index + 1) % THINKING_ACTIVITIES.length];
}
diff --git a/packages/ui/src/features/mcp-apps/components/McpToolView.tsx b/packages/ui/src/features/mcp-apps/components/McpToolView.tsx
index 2a40dc99bc..6e99d80395 100644
--- a/packages/ui/src/features/mcp-apps/components/McpToolView.tsx
+++ b/packages/ui/src/features/mcp-apps/components/McpToolView.tsx
@@ -2,7 +2,7 @@ import { Plugs } from "@phosphor-icons/react";
import {
getPostHogExecDisplay,
isPostHogExecTool,
-} from "../../posthog-mcp/utils/posthog-exec-display";
+} from "@posthog/core/sessions/posthogExecDisplay";
import { useChatThreadChrome } from "../../sessions/components/chat-thread/chatThreadChrome";
import { ToolRow } from "../../sessions/components/session-update/ToolRow";
import {
diff --git a/packages/ui/src/features/permissions/McpPermission.tsx b/packages/ui/src/features/permissions/McpPermission.tsx
index 6cf59f90cf..cd5ee5961d 100644
--- a/packages/ui/src/features/permissions/McpPermission.tsx
+++ b/packages/ui/src/features/permissions/McpPermission.tsx
@@ -1,9 +1,9 @@
-import { mcpToolKey, readMcpToolDescriptor } from "@posthog/shared";
import {
formatPosthogExecBody,
getPostHogExecDisplay,
isPostHogExecTool,
-} from "@posthog/ui/features/posthog-mcp/utils/posthog-exec-display";
+} from "@posthog/core/sessions/posthogExecDisplay";
+import { mcpToolKey, readMcpToolDescriptor } from "@posthog/shared";
import { formatInput } from "@posthog/ui/features/sessions/components/session-update/toolCallUtils";
import { ActionSelector } from "@posthog/ui/primitives/ActionSelector";
import { Box, Code } from "@radix-ui/themes";
diff --git a/packages/ui/src/features/posthog-mcp/utils/posthog-exec-display.test.ts b/packages/ui/src/features/posthog-mcp/utils/posthog-exec-display.test.ts
index 08aa1b6f74..5a8603b301 100644
--- a/packages/ui/src/features/posthog-mcp/utils/posthog-exec-display.test.ts
+++ b/packages/ui/src/features/posthog-mcp/utils/posthog-exec-display.test.ts
@@ -1,9 +1,9 @@
-import { describe, expect, it } from "vitest";
import {
formatPosthogExecBody,
getPostHogExecDisplay,
isPostHogExecTool,
-} from "./posthog-exec-display";
+} from "@posthog/core/sessions/posthogExecDisplay";
+import { describe, expect, it } from "vitest";
describe("isPostHogExecTool", () => {
it("matches the bare posthog exec tool", () => {
diff --git a/packages/ui/src/features/posthog-mcp/utils/posthog-exec-display.ts b/packages/ui/src/features/posthog-mcp/utils/posthog-exec-display.ts
deleted file mode 100644
index 50cda82a30..0000000000
--- a/packages/ui/src/features/posthog-mcp/utils/posthog-exec-display.ts
+++ /dev/null
@@ -1,136 +0,0 @@
-/**
- * The PostHog MCP exposes a single `exec` dispatcher that runs CLI-style
- * subcommands. Generic MCP rendering would show this as
- * `posthog - exec (MCP) {"command":"call execute-sql {…}"}` — pure plumbing
- * with the dispatched action buried inside a JSON wrapper.
- *
- * These helpers pull the action out of the `command` string so the row can
- * read `posthog - execute-sql {…}` (call), `posthog - Read execute-sql`
- * (info), `posthog - Inspect query-trends.series` (schema),
- * `posthog - Search tools query-` (search), or `posthog - List tools`
- * (tools) instead.
- *
- * Supported verbs (per the `exec` tool description):
- * tools — list every tool
- * search — search by name/title/description
- * info — show description + input schema
- * schema [field_path] — drill into a specific field
- * call [--json] — invoke a tool
- */
-
-import { parseMcpToolName } from "@posthog/shared";
-
-// A PostHog MCP server name: optional `plugin_` prefix, `posthog`, then any
-// number of `_` parts (e.g. `posthog`, `posthog_cloud`,
-// `plugin_posthog_posthog`). The `exec` dispatcher lives on these servers.
-const POSTHOG_SERVER_RE = /^(?:plugin_)?posthog(?:_[^_]+)*$/;
-
-const POSTHOG_VERB_RE =
- /^\s*(tools|search|info|schema|call)(?:\s+([\s\S]*))?\s*$/;
-const POSTHOG_CALL_BODY_RE = /^(?:--json\s+)?([a-zA-Z0-9_-]+)\s*([\s\S]*)$/;
-const POSTHOG_TOOL_NAME_RE = /^([a-zA-Z0-9_-]+)\s*([\s\S]*)$/;
-
-export interface PostHogExecDisplay {
- /** Replaces the tool name in the title — e.g. "execute-sql", "Read execute-sql". */
- label: string;
- /** Args to show as the input preview, undefined when there is none to display. */
- input?: string;
-}
-
-export function isPostHogExecTool(toolName: string): boolean {
- const mcp = parseMcpToolName(toolName);
- return !!mcp && mcp.tool === "exec" && POSTHOG_SERVER_RE.test(mcp.server);
-}
-
-export function getPostHogExecDisplay(
- toolInput: unknown,
-): PostHogExecDisplay | null {
- if (!toolInput || typeof toolInput !== "object") return null;
- const obj = toolInput as { command?: unknown; input?: unknown };
-
- if (typeof obj.command !== "string") return null;
- const verbMatch = obj.command.match(POSTHOG_VERB_RE);
- if (!verbMatch) return null;
-
- const verb = verbMatch[1] as "tools" | "search" | "info" | "schema" | "call";
- const rest = (verbMatch[2] ?? "").trim();
- const explicitInput = readExplicitInput(obj.input);
-
- switch (verb) {
- case "tools":
- // `tools` returns names only, not full schemas — "List", not "Read".
- return { label: "List tools", input: undefined };
-
- case "search":
- return {
- label: "Search tools",
- input: explicitInput ?? (rest.length > 0 ? rest : undefined),
- };
-
- case "info":
- // `info ` — fold the tool name into the label so the args slot stays clean.
- return rest.length > 0
- ? { label: `Read ${rest}`, input: undefined }
- : { label: "Read tool", input: undefined };
-
- case "schema": {
- // `schema [field_path]` is the drill-down verb. Fold the
- // tool + path into a dotted locator so it reads as one path.
- const m = rest.match(POSTHOG_TOOL_NAME_RE);
- if (!m) return { label: "Inspect schema", input: undefined };
- const subTool = m[1];
- const fieldPath = (m[2] ?? "").trim();
- const path =
- explicitInput ?? (fieldPath.length > 0 ? fieldPath : undefined);
- return {
- label: path
- ? `Inspect ${subTool}.${path}`
- : `Inspect ${subTool} fields`,
- input: undefined,
- };
- }
-
- case "call": {
- // `call [--json] [json_input]` — collapse the verb, surface the
- // sub-tool as the label and the JSON body as args.
- const m = rest.match(POSTHOG_CALL_BODY_RE);
- if (!m) return null;
- const subTool = m[1];
- const args = (m[2] ?? "").trim();
- return {
- label: subTool,
- input: explicitInput ?? (args.length > 0 ? args : undefined),
- };
- }
- }
-}
-
-function readExplicitInput(value: unknown): string | undefined {
- if (value === undefined || value === null) return undefined;
- if (typeof value === "string") return value.trim() || undefined;
- try {
- return JSON.stringify(value);
- } catch {
- return undefined;
- }
-}
-
-/**
- * Pretty-prints the unwrapped exec args for display in the permission dialog
- * body — JSON payloads (the `call` case) render multi-line; non-JSON args
- * (e.g. a `search` regex) pass through unchanged.
- */
-export function formatPosthogExecBody(
- input: string | undefined,
-): string | undefined {
- if (!input) return undefined;
- try {
- const parsed = JSON.parse(input);
- if (parsed && typeof parsed === "object") {
- return JSON.stringify(parsed, null, 2);
- }
- } catch {
- // not JSON — fall through and show raw
- }
- return input;
-}
diff --git a/packages/ui/src/features/sessions/components/GeneratingIndicator.tsx b/packages/ui/src/features/sessions/components/GeneratingIndicator.tsx
index 58ea8697a9..47343950d1 100644
--- a/packages/ui/src/features/sessions/components/GeneratingIndicator.tsx
+++ b/packages/ui/src/features/sessions/components/GeneratingIndicator.tsx
@@ -1,110 +1,19 @@
import { Brain, Circle } from "@phosphor-icons/react";
+import {
+ pickNextThinkingActivity,
+ pickThinkingActivity,
+} from "@posthog/core/sessions/thinkingActivities";
import { Flex, Text } from "@radix-ui/themes";
import { useEffect, useRef, useState } from "react";
-const THINKING_MESSAGES = [
- "Booping",
- "Crunching",
- "Digging",
- "Fetching",
- "Inferring",
- "Indexing",
- "Juggling",
- "Noodling",
- "Peeking",
- "Percolating",
- "Poking",
- "Pondering",
- "Scanning",
- "Scrambling",
- "Sifting",
- "Sniffing",
- "Spelunking",
- "Tinkering",
- "Unraveling",
- "Decoding",
- "Trekking",
- "Sorting",
- "Trimming",
- "Mulling",
- "Surfacing",
- "Rummaging",
- "Scouting",
- "Scouring",
- "Threading",
- "Hunting",
- "Swizzling",
- "Grokking",
- "Hedging",
- "Scheming",
- "Unfurling",
- "Puzzling",
- "Dissecting",
- "Stacking",
- "Snuffling",
- "Hashing",
- "Clustering",
- "Teasing",
- "Cranking",
- "Merging",
- "Snooping",
- "Rewiring",
- "Bundling",
- "Linking",
- "Mapping",
- "Tickling",
- "Flicking",
- "Hopping",
- "Rolling",
- "Zipping",
- "Twisting",
- "Blooming",
- "Sparking",
- "Nesting",
- "Looping",
- "Wiring",
- "Snipping",
- "Zoning",
- "Tracing",
- "Warping",
- "Twinkling",
- "Flipping",
- "Priming",
- "Snagging",
- "Scuttling",
- "Framing",
- "Sharpening",
- "Flibbertigibbeting",
- "Kerfuffling",
- "Dithering",
- "Discombobulating",
- "Rambling",
- "Befuddling",
- "Waffling",
- "Muckling",
- "Hobnobbing",
- "Galumphing",
- "Puttering",
- "Whiffling",
- "Thinking",
-];
-
function getRandomThinkingMessage(): string {
- return THINKING_MESSAGES[
- Math.floor(Math.random() * THINKING_MESSAGES.length)
- ];
+ return pickThinkingActivity(Math.random());
}
/** Pick a new word that differs from the current one, so consecutive changes
* always read as a change. */
function getNextThinkingMessage(current: string): string {
- if (THINKING_MESSAGES.length <= 1) return THINKING_MESSAGES[0];
- let next = current;
- while (next === current) {
- next =
- THINKING_MESSAGES[Math.floor(Math.random() * THINKING_MESSAGES.length)];
- }
- return next;
+ return pickNextThinkingActivity(current, Math.random());
}
export function formatDuration(ms: number, fractionDigits = 2): string {