Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions web/mobile/src/features/playground/PlaygroundCompare.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type {ComponentProps} from "react"

import {CompareLayout} from "@agenta/playground-ui/panels/compare"

import {RevisionConfig} from "./RevisionConfig"
import {useCloseRevision} from "./useCloseRevision"
import {usePlaygroundTraceFooter} from "./usePlaygroundTraceFooter"

type ColumnProps = Omit<
ComponentProps<typeof RevisionConfig>,
"revisionId" | "readOnly" | "onClose"
>

/**
* The compare view: two or more revisions side by side. It loads on demand, the first time the
* page shows more than one revision.
*/
export const PlaygroundCompare = ({
revisionIds,
deletedId,
column,
}: {
revisionIds: string[]
/** A revision the server deleted, read only until it leaves the page. */
deletedId: string | null
column: ColumnProps
}) => {
const {close, modal} = useCloseRevision()
const renderTraceFooter = usePlaygroundTraceFooter()

return (
<>
<CompareLayout
revisionIds={revisionIds}
onClose={close}
renderTraceFooter={renderTraceFooter}
renderConfig={(revisionId) => (
<RevisionConfig
{...column}
revisionId={revisionId}
readOnly={deletedId === revisionId}
onClose={close}
/>
)}
/>
{modal}
</>
)
}
7 changes: 6 additions & 1 deletion web/mobile/src/features/playground/PlaygroundHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import type {ReactNode} from "react"

import {NavDrawer} from "../nav/NavDrawer"

/** The playground's top bar: the app's name, and what this page is. */
/** The playground's top bar: the app's name, what this page is, and the page's actions. */
export const PlaygroundHeader = ({
workspaceId,
projectId,
appName,
actions,
}: {
workspaceId: string
projectId: string
appName: string | null
actions?: ReactNode
}) => (
<header className="flex h-12 shrink-0 items-center gap-2 border-b border-border px-4">
<NavDrawer workspaceId={workspaceId} projectId={projectId} />
<h1 className="m-0 min-w-0 truncate text-sm font-semibold text-foreground">{appName}</h1>
<span className="shrink-0 text-sm text-muted-foreground">Playground</span>
{actions ? <div className="ml-auto flex shrink-0 items-center gap-2">{actions}</div> : null}
</header>
)
15 changes: 7 additions & 8 deletions web/mobile/src/features/playground/PlaygroundScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const PlaygroundScreen = ({
const store = useStore()
const selection = usePlaygroundSelection({appId})
const leaveDialog = usePlaygroundLeaveGuard()
const {status, appKind, primaryRevisionId} = selection
const {status, appKind, primaryRevisionId, revisionIds} = selection

useEffect(() => {
preloadEditorPlugins()
Expand All @@ -46,16 +46,15 @@ export const PlaygroundScreen = ({
void router.replace(agentEntryHref(`/w/${workspaceId}/p/${projectId}`, appId))
}, [appId, appKind, projectId, router, workspaceId])

// A commit makes a new revision: the playground follows it.
// A commit makes a new revision: it takes the place of the revision it was made from.
const followCommit = useCallback(
(revisionId: string) => {
if (!primaryRevisionId) return
(fromRevisionId: string, toRevisionId: string) => {
store.set(playgroundController.actions.switchEntity, {
currentEntityId: primaryRevisionId,
newEntityId: revisionId,
currentEntityId: fromRevisionId,
newEntityId: toRevisionId,
})
},
[primaryRevisionId, store],
[store],
)

if (status === "not-found" || appKind === "evaluator") return <PageNotFound />
Expand All @@ -73,7 +72,7 @@ export const PlaygroundScreen = ({
projectId={projectId}
appId={appId}
appName={selection.appName}
revisionId={primaryRevisionId}
revisionIds={revisionIds}
onWorkflowRevisionCommitted={followCommit}
/>
)}
Expand Down
119 changes: 68 additions & 51 deletions web/mobile/src/features/playground/PlaygroundWorkspace.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {useState} from "react"
import {useCallback, useState} from "react"

import type {ConfigViewMode} from "@agenta/entity-ui/drill-in"
import {ConfigPanel, RevisionSwitcher} from "@agenta/playground-ui/panels/config"
import {CompareButton} from "@agenta/playground-ui/panels/config"
import dynamic from "next/dynamic"

import {DrillInBridgeProvider} from "../chat/DrillInBridgeProvider"
Expand All @@ -10,93 +10,110 @@ import {PlaygroundFocusDrawerHost} from "./PlaygroundFocusDrawerHost"
import {PlaygroundHeader} from "./PlaygroundHeader"
import {PlaygroundRunPane} from "./PlaygroundRunPane"
import {PlaygroundSplit} from "./PlaygroundSplit"
import {RevisionActions} from "./RevisionActions"
import {RevisionConfig} from "./RevisionConfig"
import {useSwitchRevision} from "./useSwitchRevision"

// The tools section of a prompt opens the tool catalog; it loads when first opened.
const CatalogDrawer = dynamic(
() => import("@agenta/entity-ui/gatewayTool").then((m) => m.CatalogDrawer),
{ssr: false},
)
// The compare view loads the first time the page shows two revisions.
const PlaygroundCompare = dynamic(
() => import("./PlaygroundCompare").then((m) => m.PlaygroundCompare),
{ssr: false},
)
// The wand on a prompt section opens it; it loads when first opened.
const RefinePromptDialog = dynamic(
() => import("@agenta/playground-ui/panels/refine").then((m) => m.RefinePromptDialog),
{ssr: false},
)

/**
* The loaded playground: the top bar, then the selected revision's config beside its runs.
* A commit made in the config calls `onWorkflowRevisionCommitted` with the new revision.
* The loaded playground: the top bar, then the selected revision's config beside its runs, or,
* with two or more revisions, the compare view. A commit in a revision's config shows the new
* revision in that revision's place.
*/
export const PlaygroundWorkspace = ({
workspaceId,
projectId,
appId,
appName,
revisionId,
revisionIds,
onWorkflowRevisionCommitted,
}: {
workspaceId: string
projectId: string
appId: string
appName: string | null
revisionId: string
onWorkflowRevisionCommitted: (revisionId: string) => void
/** The revisions on screen, in order. Never empty. */
revisionIds: string[]
onWorkflowRevisionCommitted: (fromRevisionId: string, toRevisionId: string) => void
}) => {
// Form, JSON or YAML: kept while the page is open, as in the old playground.
const [viewMode, setViewMode] = useState<ConfigViewMode>("form")
const {switchTo, modal: switchDialog} = useSwitchRevision(revisionId)
const {switchTo, modal: switchDialog} = useSwitchRevision()
// A revision the server has deleted is read only from that moment. It stays on screen until
// the lists load again and the page takes it out, and an edit made then would be lost.
const [deletedId, setDeletedId] = useState<string | null>(null)
const readOnly = deletedId === revisionId
const [refineKey, setRefineKey] = useState<string | null>(null)
const [refine, setRefine] = useState<{revisionId: string; promptKey: string} | null>(null)
const primaryId = revisionIds[0]
// Only an agent's model settings commit from inside the editor; a prompt commits from its bar.
const followEditorCommit = useCallback(
(revisionId: string) => onWorkflowRevisionCommitted(primaryId, revisionId),
[onWorkflowRevisionCommitted, primaryId],
)
const column = {
workspaceId,
projectId,
appId,
viewMode,
onViewModeChange: setViewMode,
onSwitch: switchTo,
onCommitted: onWorkflowRevisionCommitted,
onArchived: setDeletedId,
onRefinePrompt: (revisionId: string, promptKey: string) =>
setRefine({revisionId, promptKey}),
}

return (
<div className="flex h-full min-h-0 flex-col bg-background">
<PlaygroundHeader workspaceId={workspaceId} projectId={projectId} appName={appName} />
<PlaygroundSplit
config={
<DrillInBridgeProvider
projectId={projectId}
onWorkflowRevisionCommitted={onWorkflowRevisionCommitted}
>
<ConfigPanel
revisionId={revisionId}
readOnly={readOnly}
viewMode={viewMode}
onViewModeChange={setViewMode}
onRefinePrompt={setRefineKey}
switcher={
<RevisionSwitcher
appId={appId}
revisionId={revisionId}
onSwitch={switchTo}
/>
}
actions={
<RevisionActions
workspaceId={workspaceId}
projectId={projectId}
appId={appId}
revisionId={revisionId}
readOnly={readOnly}
onCommitted={onWorkflowRevisionCommitted}
onArchived={setDeletedId}
/>
}
/>
</DrillInBridgeProvider>
}
runs={<PlaygroundRunPane revisionId={revisionId} />}
<PlaygroundHeader
workspaceId={workspaceId}
projectId={projectId}
appName={appName}
actions={<CompareButton appId={appId} />}
/>
<DrillInBridgeProvider
projectId={projectId}
onWorkflowRevisionCommitted={followEditorCommit}
>
{revisionIds.length > 1 ? (
<PlaygroundCompare
revisionIds={revisionIds}
deletedId={deletedId}
column={column}
/>
) : (
<PlaygroundSplit
config={
<RevisionConfig
{...column}
revisionId={primaryId}
readOnly={deletedId === primaryId}
/>
}
runs={<PlaygroundRunPane revisionId={primaryId} />}
/>
)}
</DrillInBridgeProvider>
<CatalogDrawer />
{refineKey ? (
{refine ? (
<RefinePromptDialog
key={`${revisionId}:${refineKey}`}
revisionId={revisionId}
promptKey={refineKey}
onClose={() => setRefineKey(null)}
key={`${refine.revisionId}:${refine.promptKey}`}
revisionId={refine.revisionId}
promptKey={refine.promptKey}
onClose={() => setRefine(null)}
/>
) : null}
{switchDialog}
Expand Down
4 changes: 4 additions & 0 deletions web/mobile/src/features/playground/RevisionActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export const RevisionActions = ({
readOnly,
onCommitted,
onArchived,
onClose,
}: {
workspaceId: string
projectId: string
Expand All @@ -89,6 +90,8 @@ export const RevisionActions = ({
onCommitted: (revisionId: string) => void
/** The server deleted this revision. The page takes it out after the lists load again. */
onArchived: (revisionId: string) => void
/** Takes the revision off the compare view; absent in the single view. */
onClose?: () => void
}) => {
const store = useStore()
const queryClient = useQueryClient()
Expand Down Expand Up @@ -226,6 +229,7 @@ export const RevisionActions = ({
onRevert={discard}
onDeploy={() => setOpen({kind: "deploy", revision: shownRevision()})}
onDelete={() => setOpen({kind: "delete", revision: shownRevision()})}
onClose={onClose}
/>
</>
)}
Expand Down
66 changes: 66 additions & 0 deletions web/mobile/src/features/playground/RevisionConfig.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type {ConfigViewMode} from "@agenta/entity-ui/drill-in"
import {ConfigPanel, RevisionSwitcher} from "@agenta/playground-ui/panels/config"

import {RevisionActions} from "./RevisionActions"

/**
* One revision's config: its bar (switcher, Deploy, Commit, menu) and its editor. The single view
* shows one; the compare view shows one per column, each with "Close panel" in its menu.
*/
export const RevisionConfig = ({
workspaceId,
projectId,
appId,
revisionId,
readOnly,
viewMode,
onViewModeChange,
onSwitch,
onCommitted,
onArchived,
onRefinePrompt,
onClose,
}: {
workspaceId: string
projectId: string
appId: string
revisionId: string
readOnly: boolean
viewMode: ConfigViewMode
onViewModeChange: (mode: ConfigViewMode) => void
/** Shows another revision in place of this one. */
onSwitch: (currentRevisionId: string, newRevisionId: string) => void
/** A commit of this revision made a new revision. */
onCommitted: (fromRevisionId: string, toRevisionId: string) => void
onArchived: (revisionId: string) => void
/** The wand on one of this revision's prompts: opens the refine dialog for it. */
onRefinePrompt: (revisionId: string, promptKey: string) => void
onClose?: (revisionId: string) => void
}) => (
<ConfigPanel
revisionId={revisionId}
readOnly={readOnly}
viewMode={viewMode}
onViewModeChange={onViewModeChange}
onRefinePrompt={(promptKey) => onRefinePrompt(revisionId, promptKey)}
switcher={
<RevisionSwitcher
appId={appId}
revisionId={revisionId}
onSwitch={(id) => onSwitch(revisionId, id)}
/>
}
actions={
<RevisionActions
workspaceId={workspaceId}
projectId={projectId}
appId={appId}
revisionId={revisionId}
readOnly={readOnly}
onCommitted={(id) => onCommitted(revisionId, id)}
onArchived={onArchived}
onClose={onClose ? () => onClose(revisionId) : undefined}
/>
}
/>
)
Loading
Loading