Skip to content

Commit 3174647

Browse files
committed
playground ui tweaks
1 parent 14a2921 commit 3174647

File tree

2 files changed

+118
-50
lines changed
  • apps/webapp/app/routes
    • _app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.playground.$agentParam
    • _app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.playground

2 files changed

+118
-50
lines changed

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.playground.$agentParam/route.tsx

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
TrashIcon,
88
} from "@heroicons/react/20/solid";
99
import { type MetaFunction } from "@remix-run/node";
10-
import { Link, useFetcher, useNavigate } from "@remix-run/react";
10+
import { Link, useFetcher, useNavigate, useRouteLoaderData } from "@remix-run/react";
1111
import { typedjson, useTypedLoaderData } from "remix-typedjson";
1212
import { type LoaderFunctionArgs } from "@remix-run/server-runtime";
1313
import { useCallback, useEffect, useRef, useState } from "react";
@@ -46,7 +46,8 @@ import { findProjectBySlug } from "~/models/project.server";
4646
import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server";
4747
import { playgroundPresenter } from "~/presenters/v3/PlaygroundPresenter.server";
4848
import { requireUserId } from "~/services/session.server";
49-
import { EnvironmentParamSchema } from "~/utils/pathBuilder";
49+
import { Select, SelectItem } from "~/components/primitives/Select";
50+
import { EnvironmentParamSchema, v3PlaygroundAgentPath } from "~/utils/pathBuilder";
5051
import { env as serverEnv } from "~/env.server";
5152
import { generateJWT as internal_generateJWT } from "@trigger.dev/core/v3";
5253
import { extractJwtSigningSecretKey } from "~/services/realtime/jwtAuth.server";
@@ -159,9 +160,16 @@ export default function PlaygroundAgentPage() {
159160
return <PlaygroundChat key={conversationKey} />;
160161
}
161162

163+
const PARENT_ROUTE_ID =
164+
"routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.playground";
165+
162166
function PlaygroundChat() {
163167
const { agent, apiOrigin, recentConversations, activeConversation } =
164168
useTypedLoaderData<typeof loader>();
169+
const parentData = useRouteLoaderData(PARENT_ROUTE_ID) as
170+
| { agents: Array<{ slug: string }> }
171+
| undefined;
172+
const agents = parentData?.agents ?? [];
165173
const navigate = useNavigate();
166174
const organization = useOrganization();
167175
const project = useProject();
@@ -391,8 +399,32 @@ function PlaygroundChat() {
391399
{/* Header */}
392400
<div className="flex items-center justify-between border-b border-grid-bright px-4 py-2">
393401
<div className="flex items-center gap-2">
394-
<CpuChipIcon className="size-4 text-indigo-500" />
395-
<span className="text-sm font-medium text-text-bright">{agent.slug}</span>
402+
<Select
403+
value={agent.slug}
404+
setValue={(slug) => {
405+
if (slug && typeof slug === "string" && slug !== agent.slug) {
406+
navigate(v3PlaygroundAgentPath(organization, project, environment, slug));
407+
}
408+
}}
409+
icon={<CpuChipIcon className="size-4 text-indigo-500" />}
410+
text={(val) => val || undefined}
411+
variant="tertiary/small"
412+
items={agents}
413+
filter={(item, search) =>
414+
item.slug.toLowerCase().includes(search.toLowerCase())
415+
}
416+
>
417+
{(matches) =>
418+
matches.map((a) => (
419+
<SelectItem key={a.slug} value={a.slug}>
420+
<div className="flex items-center gap-2">
421+
<CpuChipIcon className="size-3.5 text-indigo-500" />
422+
<span>{a.slug}</span>
423+
</div>
424+
</SelectItem>
425+
))
426+
}
427+
</Select>
396428
<Badge variant="extra-small">{formatAgentType(agent.type)}</Badge>
397429
</div>
398430
<div className="flex items-center gap-2">

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.playground/route.tsx

Lines changed: 82 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
import { CpuChipIcon } from "@heroicons/react/20/solid";
1+
import { BookOpenIcon, CpuChipIcon } from "@heroicons/react/20/solid";
22
import { json, type MetaFunction } from "@remix-run/node";
33
import { Outlet, useNavigate, useParams, useLoaderData } from "@remix-run/react";
44
import { type LoaderFunctionArgs } from "@remix-run/server-runtime";
5+
import { CodeBlock } from "~/components/code/CodeBlock";
6+
import { InlineCode } from "~/components/code/InlineCode";
57
import { MainCenteredContainer, PageBody, PageContainer } from "~/components/layout/AppLayout";
8+
import { LinkButton } from "~/components/primitives/Buttons";
69
import { Header2 } from "~/components/primitives/Headers";
7-
import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader";
10+
import { InfoPanel } from "~/components/primitives/InfoPanel";
11+
import { NavBar, PageTitle } from "~/components/primitives/PageHeader";
812
import { Paragraph } from "~/components/primitives/Paragraph";
913
import {
1014
Select,
@@ -15,12 +19,9 @@ import { useOrganization } from "~/hooks/useOrganizations";
1519
import { useProject } from "~/hooks/useProject";
1620
import { findProjectBySlug } from "~/models/project.server";
1721
import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server";
18-
import {
19-
type PlaygroundAgent,
20-
playgroundPresenter,
21-
} from "~/presenters/v3/PlaygroundPresenter.server";
22+
import { playgroundPresenter } from "~/presenters/v3/PlaygroundPresenter.server";
2223
import { requireUserId } from "~/services/session.server";
23-
import { EnvironmentParamSchema, v3PlaygroundAgentPath } from "~/utils/pathBuilder";
24+
import { docsPath, EnvironmentParamSchema, v3PlaygroundAgentPath } from "~/utils/pathBuilder";
2425

2526
export const meta: MetaFunction = () => {
2627
return [{ title: "Playground | Trigger.dev" }];
@@ -64,15 +65,52 @@ export default function PlaygroundPage() {
6465
<PageTitle title="Playground" />
6566
</NavBar>
6667
<PageBody>
67-
<MainCenteredContainer>
68-
<div className="flex flex-col items-center gap-4 py-20">
69-
<CpuChipIcon className="size-12 text-indigo-500" />
70-
<Header2>No agents deployed</Header2>
71-
<Paragraph variant="small" className="max-w-md text-center">
72-
Create a chat agent using <code>chat.agent()</code> from{" "}
73-
<code>@trigger.dev/sdk/ai</code> and deploy it to see it here.
68+
<MainCenteredContainer className="max-w-2xl">
69+
<InfoPanel
70+
title="Create your first agent"
71+
icon={CpuChipIcon}
72+
iconClassName="text-indigo-500"
73+
panelClassName="max-w-2xl"
74+
accessory={
75+
<LinkButton
76+
to={docsPath("ai-chat/overview")}
77+
variant="docs/small"
78+
LeadingIcon={BookOpenIcon}
79+
>
80+
Agent docs
81+
</LinkButton>
82+
}
83+
>
84+
<Paragraph spacing variant="small">
85+
The Playground lets you test your AI agents with an interactive chat interface,
86+
realtime streaming, and conversation history.
7487
</Paragraph>
75-
</div>
88+
<Paragraph spacing variant="small">
89+
Define a chat agent using{" "}
90+
<InlineCode variant="small">chat.agent()</InlineCode>:
91+
</Paragraph>
92+
<CodeBlock
93+
code={`import { chat } from "@trigger.dev/sdk/ai";
94+
import { streamText } from "ai";
95+
import { openai } from "@ai-sdk/openai";
96+
97+
export const myAgent = chat.agent({
98+
id: "my-agent",
99+
run: async ({ messages, signal }) => {
100+
return streamText({
101+
model: openai("gpt-4o"),
102+
messages,
103+
abortSignal: signal,
104+
});
105+
},
106+
});`}
107+
showLineNumbers={false}
108+
showOpenInModal={false}
109+
/>
110+
<Paragraph variant="small" className="mt-2">
111+
Deploy your project and your agents will appear here ready to test.
112+
</Paragraph>
113+
</InfoPanel>
76114
</MainCenteredContainer>
77115
</PageBody>
78116
</PageContainer>
@@ -83,35 +121,6 @@ export default function PlaygroundPage() {
83121
<PageContainer>
84122
<NavBar>
85123
<PageTitle title="Playground" />
86-
<PageAccessories>
87-
<Select
88-
value={selectedAgent}
89-
setValue={(slug) => {
90-
if (slug && typeof slug === "string") {
91-
navigate(v3PlaygroundAgentPath(organization, project, environment, slug));
92-
}
93-
}}
94-
icon={<CpuChipIcon className="size-4 text-indigo-500" />}
95-
text={(val) => val || undefined}
96-
placeholder="Select an agent..."
97-
variant="tertiary/small"
98-
items={agents}
99-
filter={(item, search) =>
100-
item.slug.toLowerCase().includes(search.toLowerCase())
101-
}
102-
>
103-
{(matches) =>
104-
matches.map((agent, index) => (
105-
<SelectItem key={agent.slug} value={agent.slug}>
106-
<div className="flex items-center gap-2">
107-
<CpuChipIcon className="size-3.5 text-indigo-500" />
108-
<span>{agent.slug}</span>
109-
</div>
110-
</SelectItem>
111-
))
112-
}
113-
</Select>
114-
</PageAccessories>
115124
</NavBar>
116125
<PageBody scrollable={false}>
117126
{selectedAgent ? (
@@ -121,9 +130,36 @@ export default function PlaygroundPage() {
121130
<div className="flex flex-col items-center gap-4 py-20">
122131
<CpuChipIcon className="size-10 text-indigo-500/50" />
123132
<Header2 className="text-text-dimmed">Select an agent</Header2>
124-
<Paragraph variant="small" className="max-w-md text-center text-text-dimmed">
125-
Choose an agent from the dropdown to start a conversation.
133+
<Paragraph variant="small" className="mb-2 max-w-md text-center text-text-dimmed">
134+
Choose an agent to start a conversation.
126135
</Paragraph>
136+
<Select
137+
value={selectedAgent}
138+
setValue={(slug) => {
139+
if (slug && typeof slug === "string") {
140+
navigate(v3PlaygroundAgentPath(organization, project, environment, slug));
141+
}
142+
}}
143+
icon={<CpuChipIcon className="size-4 text-indigo-500" />}
144+
text={(val) => val || undefined}
145+
placeholder="Select an agent..."
146+
variant="tertiary/small"
147+
items={agents}
148+
filter={(item, search) =>
149+
item.slug.toLowerCase().includes(search.toLowerCase())
150+
}
151+
>
152+
{(matches) =>
153+
matches.map((agent) => (
154+
<SelectItem key={agent.slug} value={agent.slug}>
155+
<div className="flex items-center gap-2">
156+
<CpuChipIcon className="size-3.5 text-indigo-500" />
157+
<span>{agent.slug}</span>
158+
</div>
159+
</SelectItem>
160+
))
161+
}
162+
</Select>
127163
</div>
128164
</MainCenteredContainer>
129165
)}

0 commit comments

Comments
 (0)