Skip to content

Commit

Permalink
Merge pull request #1194 from Agenta-AI/issue-1152/-calling-a-variant…
Browse files Browse the repository at this point in the history
…-in-the-playground-blocks-the-UI

Calling a variant in the playground blocks the UI
  • Loading branch information
aakrem authored Jan 18, 2024
2 parents e23f139 + e423cae commit 633fc67
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 50 deletions.
157 changes: 117 additions & 40 deletions agenta-web/src/components/Playground/Views/TestView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, {useContext, useEffect, useState} from "react"
import React, {useContext, useEffect, useRef, useState} from "react"
import {Button, Input, Card, Row, Col, Space, Form} from "antd"
import {CaretRightOutlined, PlusOutlined} from "@ant-design/icons"
import {CaretRightOutlined, CloseCircleOutlined, PlusOutlined} from "@ant-design/icons"
import {callVariant} from "@/lib/services/api"
import {ChatMessage, ChatRole, GenericObject, Parameter, Variant} from "@/lib/Types"
import {batchExecute, randString, removeKeys} from "@/lib/helpers/utils"
Expand Down Expand Up @@ -109,6 +109,7 @@ interface BoxComponentProps {
onDelete?: () => void
isChatVariant?: boolean
variant: Variant
onCancel: () => void
}

const BoxComponent: React.FC<BoxComponentProps> = ({
Expand All @@ -122,6 +123,7 @@ const BoxComponent: React.FC<BoxComponentProps> = ({
onDelete,
isChatVariant = false,
variant,
onCancel,
}) => {
const {appTheme} = useAppTheme()
const classes = useStylesBox()
Expand Down Expand Up @@ -208,17 +210,29 @@ const BoxComponent: React.FC<BoxComponentProps> = ({
disabled={loading || !result}
shape="round"
/>
<Button
data-cy="testview-input-parameters-run-button"
className={`testview-run-button-${testData._id}`}
type="primary"
shape="round"
icon={<CaretRightOutlined />}
onClick={isChatVariant ? onRun : form.submit}
loading={loading}
>
Run
</Button>
{loading ? (
<Button
icon={<CloseCircleOutlined />}
type="primary"
style={{backgroundColor: "#d32f2f"}}
onClick={onCancel}
className={`testview-cancel-button-${testData._id}`}
>
Cancel
</Button>
) : (
<Button
data-cy="testview-input-parameters-run-button"
className={`testview-run-button-${testData._id}`}
type="primary"
shape="round"
icon={<CaretRightOutlined />}
onClick={isChatVariant ? onRun : form.submit}
loading={loading}
>
Run
</Button>
)}
</Col>
</Row>
{!isChatVariant && (
Expand Down Expand Up @@ -276,6 +290,15 @@ const App: React.FC<TestViewProps> = ({
}>
>(testList.map(() => ({cost: null, latency: null, usage: null})))

const abortControllersRef = useRef<AbortController[]>([])
const [isRunningAll, setIsRunningAll] = useState(false)

useEffect(() => {
return () => {
abortControllersRef.current.forEach((controller) => controller.abort())
}
}, [])

useEffect(() => {
setResultsList((prevResultsList) => {
const newResultsList = testList.map((_, index) => {
Expand Down Expand Up @@ -326,25 +349,30 @@ const App: React.FC<TestViewProps> = ({
}

const handleRun = async (index: number) => {
const controller = new AbortController()
abortControllersRef.current[index] = controller
try {
const testItem = testList[index]
if (compareMode && !isRunning[index]) {
setIsRunning(
(prevState) => {
const newState = [...prevState]
newState[index] = true
return newState
},
() => {
document
.querySelectorAll(`.testview-run-button-${testItem._id}`)
.forEach((btn) => {
if (btn.parentElement?.id !== variant.variantId) {
;(btn as HTMLButtonElement).click()
}
})
},
)
let called = false
const callback = () => {
if (called) return
called = true
document
.querySelectorAll(`.testview-run-button-${testItem._id}`)
.forEach((btn) => {
if (btn.parentElement?.id !== variant.variantId) {
;(btn as HTMLButtonElement).click()
}
})
}

setIsRunning((prevState) => {
const newState = [...prevState]
newState[index] = true
return newState
}, callback)
setTimeout(callback, 300)
}
setResultForIndex(LOADING_TEXT, index)

Expand All @@ -355,7 +383,10 @@ const App: React.FC<TestViewProps> = ({
appId || "",
variant.baseId || "",
isChatVariant ? testItem.chat : [],
controller.signal,
true,
)

// check if res is an object or string
if (typeof res === "string") {
setResultForIndex(res, index)
Expand All @@ -368,7 +399,16 @@ const App: React.FC<TestViewProps> = ({
})
}
} catch (e) {
setResultForIndex(`❌ ${getErrorMessage(e)}`, index)
if (!controller.signal.aborted) {
setResultForIndex(`❌ ${getErrorMessage(e)}`, index)
} else {
setResultForIndex("", index)
setAdditionalDataList((prev) => {
const newDataList = [...prev]
newDataList[index] = {cost: null, latency: null, usage: null}
return newDataList
})
}
} finally {
setIsRunning((prevState) => {
const newState = [...prevState]
Expand All @@ -378,13 +418,38 @@ const App: React.FC<TestViewProps> = ({
}
}

const handleRunAll = () => {
const handleCancel = (index: number) => {
if (abortControllersRef.current[index]) {
abortControllersRef.current[index].abort()
}
if (compareMode && isRunning[index]) {
const testItem = testList[index]

document.querySelectorAll(`.testview-cancel-button-${testItem._id}`).forEach((btn) => {
if (btn.parentElement?.id !== variant.variantId) {
;(btn as HTMLButtonElement).click()
}
})
}
}

const handleCancelAll = () => {
const funcs: Function[] = []
rootRef.current
?.querySelectorAll("[class*=testview-cancel-button-]")
.forEach((btn) => funcs.push(() => (btn as HTMLButtonElement).click()))
batchExecute(funcs)
}

const handleRunAll = async () => {
const funcs: Function[] = []
rootRef.current
?.querySelectorAll("[data-cy=testview-input-parameters-run-button]")
.forEach((btn) => funcs.push(() => (btn as HTMLButtonElement).click()))

batchExecute(funcs)
setIsRunningAll(true)
await batchExecute(funcs)
setIsRunningAll(false)
}

const handleAddRow = () => {
Expand Down Expand Up @@ -432,14 +497,25 @@ const App: React.FC<TestViewProps> = ({
<Space size={10}>
<LoadTestsModal onLoad={onLoadTests} />

<Button
type="primary"
size="middle"
className={classes.runAllBtn}
onClick={handleRunAll}
>
Run all
</Button>
{!isRunningAll ? (
<Button
type="primary"
size="middle"
className={classes.runAllBtn}
onClick={handleRunAll}
>
Run all
</Button>
) : (
<Button
size="middle"
type="primary"
style={{backgroundColor: "#d32f2f"}}
onClick={handleCancelAll}
>
Cancel All
</Button>
)}
</Space>
</div>

Expand All @@ -463,6 +539,7 @@ const App: React.FC<TestViewProps> = ({
onDelete={testList.length >= 2 ? () => handleDeleteRow(index) : undefined}
isChatVariant={isChatVariant}
variant={variant}
onCancel={() => handleCancel(index)}
/>
))}
<Button
Expand Down
10 changes: 3 additions & 7 deletions agenta-web/src/hooks/useStateCallback.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {SetStateAction, useCallback, useEffect, useRef, useState} from "react"
import {SetStateAction, useCallback, useRef, useState} from "react"
import {useUpdateEffect} from "usehooks-ts"

type Callback<T> = (value?: T) => void
export type DispatchWithCallback<T> = (value: T, callback?: Callback<T>) => void
Expand All @@ -15,7 +16,6 @@ function useStateCallback<T>(
const [state, _setState] = useState(initialState)

const callbackRef = useRef<Callback<T>>()
const isFirstCallbackCall = useRef<boolean>(true)

const setState = useCallback(
(setStateAction: SetStateAction<T>, callback?: Callback<T>): void => {
Expand All @@ -25,11 +25,7 @@ function useStateCallback<T>(
[],
)

useEffect(() => {
if (isFirstCallbackCall.current) {
isFirstCallbackCall.current = false
return
}
useUpdateEffect(() => {
typeof callbackRef.current === "function" && callbackRef.current(state)
}, [state])

Expand Down
14 changes: 11 additions & 3 deletions agenta-web/src/lib/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,14 @@ export async function callVariant(
appId: string,
baseId: string,
chatMessages?: ChatMessage[],
signal?: AbortSignal,
ignoreAxiosError?: boolean,
) {
const isChatVariant = Array.isArray(chatMessages) && chatMessages.length > 0
// Separate input parameters into two dictionaries based on the 'input' property
const mainInputParams: Record<string, string> = {} // Parameters with input = true
const secondaryInputParams: Record<string, string> = {} // Parameters with input = false

for (let key of Object.keys(inputParametersDict)) {
const paramDefinition = inputParamDefinition.find((param) => param.name === key)

Expand Down Expand Up @@ -120,9 +123,14 @@ export async function callVariant(

const appContainerURI = await getAppContainerURL(appId, undefined, baseId)

return axios.post(`${appContainerURI}/generate`, requestBody).then((res) => {
return res.data
})
return axios
.post(`${appContainerURI}/generate`, requestBody, {
signal,
_ignoreError: ignoreAxiosError,
} as any)
.then((res) => {
return res.data
})
}

/**
Expand Down

0 comments on commit 633fc67

Please sign in to comment.