mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-16 18:25:17 +02:00
Remove old scenarios files
This commit is contained in:
parent
2f7688b1a6
commit
6f022e3f1b
7 changed files with 35 additions and 426 deletions
|
|
@ -1,53 +0,0 @@
|
||||||
'use server';
|
|
||||||
import { ObjectId } from "mongodb";
|
|
||||||
import { scenariosCollection } from "../lib/mongodb";
|
|
||||||
import { z } from 'zod';
|
|
||||||
import { WithStringId } from "../lib/types/types";
|
|
||||||
import { Scenario } from "../lib/types/testing_types";
|
|
||||||
import { projectAuthCheck } from "./project_actions";
|
|
||||||
|
|
||||||
export async function getScenarios(projectId: string): Promise<WithStringId<z.infer<typeof Scenario>>[]> {
|
|
||||||
await projectAuthCheck(projectId);
|
|
||||||
|
|
||||||
const scenarios = await scenariosCollection.find({ projectId }).toArray();
|
|
||||||
return scenarios.map(s => ({ ...s, _id: s._id.toString() }));
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createScenario(projectId: string, name: string, description: string): Promise<string> {
|
|
||||||
await projectAuthCheck(projectId);
|
|
||||||
|
|
||||||
const now = new Date().toISOString();
|
|
||||||
const result = await scenariosCollection.insertOne({
|
|
||||||
projectId,
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
context: '', // Always empty string
|
|
||||||
lastUpdatedAt: now,
|
|
||||||
createdAt: now,
|
|
||||||
});
|
|
||||||
return result.insertedId.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateScenario(projectId: string, scenarioId: string, name: string, description: string) {
|
|
||||||
await projectAuthCheck(projectId);
|
|
||||||
|
|
||||||
await scenariosCollection.updateOne({
|
|
||||||
"_id": new ObjectId(scenarioId),
|
|
||||||
"projectId": projectId,
|
|
||||||
}, {
|
|
||||||
$set: {
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
lastUpdatedAt: new Date().toISOString(),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteScenario(projectId: string, scenarioId: string) {
|
|
||||||
await projectAuthCheck(projectId);
|
|
||||||
|
|
||||||
await scenariosCollection.deleteOne({
|
|
||||||
"_id": new ObjectId(scenarioId),
|
|
||||||
"projectId": projectId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -46,6 +46,7 @@ export async function createScenario(projectId: string, name: string, descriptio
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
context: '',
|
context: '',
|
||||||
|
criteria: '',
|
||||||
lastUpdatedAt: now,
|
lastUpdatedAt: now,
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
});
|
});
|
||||||
|
|
@ -56,7 +57,12 @@ export async function createScenario(projectId: string, name: string, descriptio
|
||||||
export async function updateScenario(
|
export async function updateScenario(
|
||||||
projectId: string,
|
projectId: string,
|
||||||
scenarioId: string,
|
scenarioId: string,
|
||||||
updates: { name?: string; description?: string; context?: string }
|
updates: {
|
||||||
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
context?: string;
|
||||||
|
criteria?: string;
|
||||||
|
}
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await projectAuthCheck(projectId);
|
await projectAuthCheck(projectId);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import { PlaygroundChat } from "../../../lib/types/types";
|
||||||
import { Workflow } from "../../../lib/types/workflow_types";
|
import { Workflow } from "../../../lib/types/workflow_types";
|
||||||
import { SimulationData } from "../../../lib/types/testing_types";
|
import { SimulationData } from "../../../lib/types/testing_types";
|
||||||
import { SimulationScenarioData } from "../../../lib/types/testing_types";
|
import { SimulationScenarioData } from "../../../lib/types/testing_types";
|
||||||
import { SimulateScenarioOption, SimulateURLOption } from "./simulation-options";
|
|
||||||
import { Chat } from "./chat";
|
import { Chat } from "./chat";
|
||||||
import { useSearchParams, useRouter } from "next/navigation";
|
import { useSearchParams, useRouter } from "next/navigation";
|
||||||
import { ActionButton, Pane } from "../workflow/pane";
|
import { ActionButton, Pane } from "../workflow/pane";
|
||||||
|
|
@ -36,7 +35,6 @@ export function App({
|
||||||
const initialChatId = useMemo(() => searchParams.get('chatId'), [searchParams]);
|
const initialChatId = useMemo(() => searchParams.get('chatId'), [searchParams]);
|
||||||
const [existingChatId, setExistingChatId] = useState<string | null>(initialChatId);
|
const [existingChatId, setExistingChatId] = useState<string | null>(initialChatId);
|
||||||
const [loadingChat, setLoadingChat] = useState<boolean>(false);
|
const [loadingChat, setLoadingChat] = useState<boolean>(false);
|
||||||
const [viewSimulationMenu, setViewSimulationMenu] = useState<boolean>(false);
|
|
||||||
const [counter, setCounter] = useState<number>(0);
|
const [counter, setCounter] = useState<number>(0);
|
||||||
const [chat, setChat] = useState<z.infer<typeof PlaygroundChat>>({
|
const [chat, setChat] = useState<z.infer<typeof PlaygroundChat>>({
|
||||||
projectId,
|
projectId,
|
||||||
|
|
@ -48,7 +46,7 @@ export function App({
|
||||||
|
|
||||||
const beginSimulation = useCallback((data: z.infer<typeof SimulationData>) => {
|
const beginSimulation = useCallback((data: z.infer<typeof SimulationData>) => {
|
||||||
setExistingChatId(null);
|
setExistingChatId(null);
|
||||||
setViewSimulationMenu(false);
|
setLoadingChat(true);
|
||||||
setCounter(counter + 1);
|
setCounter(counter + 1);
|
||||||
setChat({
|
setChat({
|
||||||
projectId,
|
projectId,
|
||||||
|
|
@ -88,7 +86,7 @@ export function App({
|
||||||
|
|
||||||
function handleNewChatButtonClick() {
|
function handleNewChatButtonClick() {
|
||||||
setExistingChatId(null);
|
setExistingChatId(null);
|
||||||
setViewSimulationMenu(false);
|
setLoadingChat(true);
|
||||||
setCounter(counter + 1);
|
setCounter(counter + 1);
|
||||||
setChat({
|
setChat({
|
||||||
projectId,
|
projectId,
|
||||||
|
|
@ -100,7 +98,7 @@ export function App({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pane title={viewSimulationMenu ? <SimulateLabel /> : "Chat"} actions={[
|
<Pane title="Chat" actions={[
|
||||||
<ActionButton
|
<ActionButton
|
||||||
key="new-chat"
|
key="new-chat"
|
||||||
icon={<MessageSquarePlusIcon size={16} />}
|
icon={<MessageSquarePlusIcon size={16} />}
|
||||||
|
|
@ -117,10 +115,10 @@ export function App({
|
||||||
</ActionButton>,
|
</ActionButton>,
|
||||||
]}>
|
]}>
|
||||||
<div className="h-full overflow-auto">
|
<div className="h-full overflow-auto">
|
||||||
{!viewSimulationMenu && loadingChat && <div className="flex justify-center items-center h-full">
|
{loadingChat && <div className="flex justify-center items-center h-full">
|
||||||
<Spinner />
|
<Spinner />
|
||||||
</div>}
|
</div>}
|
||||||
{!viewSimulationMenu && !loadingChat && <Chat
|
{!loadingChat && <Chat
|
||||||
key={existingChatId || 'chat-' + counter}
|
key={existingChatId || 'chat-' + counter}
|
||||||
chat={chat}
|
chat={chat}
|
||||||
initialChatId={existingChatId || null}
|
initialChatId={existingChatId || null}
|
||||||
|
|
@ -128,7 +126,6 @@ export function App({
|
||||||
workflow={workflow}
|
workflow={workflow}
|
||||||
messageSubscriber={messageSubscriber}
|
messageSubscriber={messageSubscriber}
|
||||||
/>}
|
/>}
|
||||||
{viewSimulationMenu && <SimulateScenarioOption beginSimulation={beginSimulation} projectId={projectId} />}
|
|
||||||
</div>
|
</div>
|
||||||
</Pane>
|
</Pane>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,245 +0,0 @@
|
||||||
'use client';
|
|
||||||
|
|
||||||
import { Button, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger, Input, Spinner, Textarea } from "@nextui-org/react";
|
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import { getScenarios, createScenario, updateScenario, deleteScenario } from "../../../actions/scenario_actions";
|
|
||||||
import { WithStringId } from "../../../lib/types/types";
|
|
||||||
import { Scenario } from "../../../lib/types/testing_types";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { EditableField } from "../../../lib/components/editable-field";
|
|
||||||
import { EllipsisVerticalIcon, PlayIcon, PlusIcon } from "lucide-react";
|
|
||||||
|
|
||||||
export function AddScenarioForm({
|
|
||||||
onAdd,
|
|
||||||
}: {
|
|
||||||
onAdd: (name: string, description: string) => Promise<void>;
|
|
||||||
}) {
|
|
||||||
const [name, setName] = useState("");
|
|
||||||
const [description, setDescription] = useState("");
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
|
||||||
const [saving, setSaving] = useState(false);
|
|
||||||
|
|
||||||
const handleAdd = async () => {
|
|
||||||
if (!name.trim() || !description.trim()) {
|
|
||||||
setError("Name and description are required");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
setSaving(true);
|
|
||||||
await onAdd(name.trim(), description.trim());
|
|
||||||
setName("");
|
|
||||||
setDescription("");
|
|
||||||
setError(null);
|
|
||||||
} catch (e) {
|
|
||||||
setError(e instanceof Error ? e.message : "Invalid input");
|
|
||||||
} finally {
|
|
||||||
setSaving(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return <div className="flex flex-col gap-2 border rounded-lg p-4 shadow-sm">
|
|
||||||
<div className="font-semibold text-gray-500">Add scenario</div>
|
|
||||||
<Input
|
|
||||||
label="Scenario Name"
|
|
||||||
labelPlacement="outside"
|
|
||||||
value={name}
|
|
||||||
placeholder="Provide a name for the scenario"
|
|
||||||
size="sm"
|
|
||||||
variant="bordered"
|
|
||||||
onChange={(e) => setName(e.target.value)}
|
|
||||||
isInvalid={!!error}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<Textarea
|
|
||||||
label="Scenario Description"
|
|
||||||
labelPlacement="outside"
|
|
||||||
value={description}
|
|
||||||
placeholder="Describe the test scenario"
|
|
||||||
size="sm"
|
|
||||||
variant="bordered"
|
|
||||||
onChange={(e) => setDescription(e.target.value)}
|
|
||||||
isInvalid={!!error}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
{error && <div className="text-red-500 text-sm">{error}</div>}
|
|
||||||
<Button
|
|
||||||
onClick={handleAdd}
|
|
||||||
isLoading={saving}
|
|
||||||
isDisabled={saving || !name.trim() || !description.trim()}
|
|
||||||
size="sm"
|
|
||||||
className="self-start"
|
|
||||||
variant="bordered"
|
|
||||||
startContent={
|
|
||||||
<PlusIcon size={16} />
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Add scenario
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ScenarioList({
|
|
||||||
projectId,
|
|
||||||
onPlay,
|
|
||||||
}: {
|
|
||||||
projectId: string;
|
|
||||||
onPlay: (scenario: z.infer<typeof Scenario>) => void;
|
|
||||||
}) {
|
|
||||||
const [scenarios, setScenarios] = useState<WithStringId<z.infer<typeof Scenario> & {
|
|
||||||
tmp?: boolean;
|
|
||||||
}>[]>([]);
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [saving, setSaving] = useState(false);
|
|
||||||
const [tmpScenarioId, setTmpScenarioId] = useState<number>(0);
|
|
||||||
const [showAddForm, setShowAddForm] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getScenarios(projectId)
|
|
||||||
.then(setScenarios)
|
|
||||||
.finally(() => setLoading(false));
|
|
||||||
}, [projectId]);
|
|
||||||
|
|
||||||
async function handleAddScenario(name: string, description: string) {
|
|
||||||
try {
|
|
||||||
const tmpId = 'tmp-' + tmpScenarioId;
|
|
||||||
setTmpScenarioId(tmpScenarioId + 1);
|
|
||||||
setSaving(true);
|
|
||||||
setShowAddForm(false);
|
|
||||||
setScenarios([...scenarios, {
|
|
||||||
_id: tmpId,
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
context: '',
|
|
||||||
projectId,
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
lastUpdatedAt: new Date().toISOString(),
|
|
||||||
tmp: true,
|
|
||||||
}]);
|
|
||||||
const id = await createScenario(projectId, name, description);
|
|
||||||
setScenarios([...scenarios, {
|
|
||||||
_id: id,
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
context: '',
|
|
||||||
projectId,
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
lastUpdatedAt: new Date().toISOString(),
|
|
||||||
tmp: false,
|
|
||||||
}]);
|
|
||||||
setError(null);
|
|
||||||
} catch (e) {
|
|
||||||
setError(e instanceof Error ? e.message : "Invalid input");
|
|
||||||
} finally {
|
|
||||||
setSaving(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
async function handleEditScenario(scenarioId: string, name: string, description: string) {
|
|
||||||
setSaving(true);
|
|
||||||
setScenarios(scenarios.map(scenario =>
|
|
||||||
scenario._id === scenarioId
|
|
||||||
? { ...scenario, name, description, context: scenario.context }
|
|
||||||
: scenario
|
|
||||||
));
|
|
||||||
await updateScenario(projectId, scenarioId, name, description);
|
|
||||||
setSaving(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleDeleteScenario(scenarioId: string) {
|
|
||||||
setSaving(true);
|
|
||||||
setScenarios(scenarios.filter(scenario => scenario._id !== scenarioId));
|
|
||||||
await deleteScenario(projectId, scenarioId);
|
|
||||||
setSaving(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<div className="flex justify-between gap-2 items-center">
|
|
||||||
<div className="font-semibold text-gray-500">Scenarios</div>
|
|
||||||
{saving && <div className="flex items-center gap-2">
|
|
||||||
<Spinner />
|
|
||||||
<div className="text-sm text-gray-500">Saving...</div>
|
|
||||||
</div>}
|
|
||||||
{!showAddForm && <Button
|
|
||||||
onClick={() => setShowAddForm(true)}
|
|
||||||
size="sm"
|
|
||||||
variant="bordered"
|
|
||||||
>
|
|
||||||
Add scenario
|
|
||||||
</Button>}
|
|
||||||
</div>
|
|
||||||
{loading && <div className="flex justify-center items-center p-8 gap-2">
|
|
||||||
<Spinner size="sm" />
|
|
||||||
<div className="text-sm text-gray-500">Loading scenarios...</div>
|
|
||||||
</div>}
|
|
||||||
|
|
||||||
{showAddForm && <AddScenarioForm onAdd={handleAddScenario} />}
|
|
||||||
|
|
||||||
{!loading && scenarios.length === 0 && <div className="flex justify-center items-center p-8 gap-2">
|
|
||||||
<div className="text-sm text-gray-500">No scenarios added</div>
|
|
||||||
</div>}
|
|
||||||
|
|
||||||
{scenarios.length > 0 && <div className="flex flex-col gap-2">
|
|
||||||
{scenarios.map((scenario) => (
|
|
||||||
<div key={scenario._id} className="flex flex-col gap-1 rounded-md shadow-sm border border-gray-300">
|
|
||||||
<div className="flex justify-between items-start">
|
|
||||||
<div className="grow font-semibold text-lg">
|
|
||||||
<EditableField
|
|
||||||
key={'name'}
|
|
||||||
placeholder="Scenario Name"
|
|
||||||
value={scenario.name}
|
|
||||||
onChange={(value) => handleEditScenario(scenario._id, value, scenario.description)}
|
|
||||||
locked={scenario.tmp}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="shrink-0 flex items-center mr-2 bg-gray-100 p-1 rounded-b-md">
|
|
||||||
<button
|
|
||||||
className="p-1 flex items-center gap-1 text-gray-500 hover:text-blue-500"
|
|
||||||
onClick={() => onPlay(scenario)}
|
|
||||||
>
|
|
||||||
<PlayIcon size={16} />
|
|
||||||
<div className="text-sm font-semibold">Run</div>
|
|
||||||
</button>
|
|
||||||
<Dropdown>
|
|
||||||
<DropdownTrigger>
|
|
||||||
<button className="p-1 flex items-center gap-1 text-gray-500 hover:text-gray-700">
|
|
||||||
<EllipsisVerticalIcon size={16} />
|
|
||||||
</button>
|
|
||||||
</DropdownTrigger>
|
|
||||||
<DropdownMenu
|
|
||||||
disabledKeys={scenario.tmp ? ['delete'] : ['']}
|
|
||||||
onAction={(key) => {
|
|
||||||
if (key === 'delete') {
|
|
||||||
handleDeleteScenario(scenario._id);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DropdownItem
|
|
||||||
key="delete"
|
|
||||||
color="danger"
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</DropdownItem>
|
|
||||||
</DropdownMenu>
|
|
||||||
</Dropdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<EditableField
|
|
||||||
key={'description'}
|
|
||||||
multiline
|
|
||||||
markdown
|
|
||||||
light
|
|
||||||
placeholder="Scenario Description"
|
|
||||||
value={scenario.description}
|
|
||||||
onChange={(value) => handleEditScenario(scenario._id, scenario.name, value)}
|
|
||||||
locked={scenario.tmp}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,108 +0,0 @@
|
||||||
'use client';
|
|
||||||
import { Input, Textarea } from "@nextui-org/react";
|
|
||||||
import { FormStatusButton } from "../../../lib/components/FormStatusButton";
|
|
||||||
import { SimulationData } from "../../../lib/types/testing_types";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { scrapeWebpage } from "../../../actions/actions";
|
|
||||||
import { ScenarioList } from "./scenario-list";
|
|
||||||
|
|
||||||
export function SimulateURLOption({
|
|
||||||
projectId,
|
|
||||||
beginSimulation,
|
|
||||||
}: {
|
|
||||||
projectId: string;
|
|
||||||
beginSimulation: (data: z.infer<typeof SimulationData>) => void;
|
|
||||||
}) {
|
|
||||||
function handleUrlSimulationSubmit(formData: FormData) {
|
|
||||||
const url = formData.get('url') as string;
|
|
||||||
// fetch article content and title
|
|
||||||
scrapeWebpage(url).then((result) => {
|
|
||||||
beginSimulation({
|
|
||||||
articleUrl: url,
|
|
||||||
articleContent: result.content,
|
|
||||||
articleTitle: result.title,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return <form action={handleUrlSimulationSubmit} className="flex flex-col gap-2">
|
|
||||||
<div>Use a URL / article link:</div>
|
|
||||||
<input type="hidden" name="projectId" value={projectId} />
|
|
||||||
<Input
|
|
||||||
variant="bordered"
|
|
||||||
placeholder="https://acme.com/articles/product-detiails"
|
|
||||||
name="url"
|
|
||||||
required
|
|
||||||
endContent={<FormStatusButton
|
|
||||||
props={{
|
|
||||||
type: "submit",
|
|
||||||
endContent: <svg className="w-6 h-6" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
|
||||||
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1" d="M19 12H5m14 0-4 4m4-4-4-4" />
|
|
||||||
</svg>,
|
|
||||||
children: "Go"
|
|
||||||
}}
|
|
||||||
/>}
|
|
||||||
/>
|
|
||||||
</form>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SimulateScenarioOption({
|
|
||||||
projectId,
|
|
||||||
beginSimulation,
|
|
||||||
}: {
|
|
||||||
projectId: string;
|
|
||||||
beginSimulation: (data: z.infer<typeof SimulationData>) => void;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<ScenarioList
|
|
||||||
projectId={projectId}
|
|
||||||
onPlay={(scenario) => beginSimulation({
|
|
||||||
scenario: scenario.description,
|
|
||||||
context: scenario.context,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SimulateChatContextOption({
|
|
||||||
projectId,
|
|
||||||
beginSimulation,
|
|
||||||
}: {
|
|
||||||
projectId: string;
|
|
||||||
beginSimulation: (data: z.infer<typeof SimulationData>) => void;
|
|
||||||
}) {
|
|
||||||
function handleChatContextSimulationSubmit(formData: FormData) {
|
|
||||||
beginSimulation({
|
|
||||||
chatMessages: formData.get('context') as string,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return <form action={handleChatContextSimulationSubmit} className="flex flex-col gap-2">
|
|
||||||
<div>Use a previous chat context:</div>
|
|
||||||
<input type="hidden" name="projectId" value={projectId} />
|
|
||||||
<Textarea
|
|
||||||
variant="bordered"
|
|
||||||
minRows={3}
|
|
||||||
maxRows={10}
|
|
||||||
required
|
|
||||||
name="context"
|
|
||||||
placeholder={JSON.stringify([
|
|
||||||
{
|
|
||||||
"role": "assistant",
|
|
||||||
"content": "Hello! How can I help you today?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "user",
|
|
||||||
"content": "Hello! I need help with..."
|
|
||||||
}
|
|
||||||
], null, 2)}
|
|
||||||
endContent={<FormStatusButton
|
|
||||||
props={{
|
|
||||||
type: "submit",
|
|
||||||
endContent: <svg className="w-6 h-6" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
|
||||||
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1" d="M19 12H5m14 0-4 4m4-4-4-4" />
|
|
||||||
</svg>,
|
|
||||||
children: "Go"
|
|
||||||
}}
|
|
||||||
/>}
|
|
||||||
/>
|
|
||||||
</form>;
|
|
||||||
}
|
|
||||||
|
|
@ -214,8 +214,10 @@ export default function SimulationApp() {
|
||||||
);
|
);
|
||||||
setActiveRun(newRun);
|
setActiveRun(newRun);
|
||||||
|
|
||||||
// Check for the NEXT_PUBLIC_ prefixed env variable
|
// Safely check for mock simulation flag
|
||||||
if (process.env.NEXT_PUBLIC_MOCK_SIMULATION_RESULTS === 'true') {
|
const shouldMock = process.env.NEXT_PUBLIC_MOCK_SIMULATION_RESULTS === 'true';
|
||||||
|
|
||||||
|
if (shouldMock) {
|
||||||
console.log('Using mock simulation...'); // Debug log
|
console.log('Using mock simulation...'); // Debug log
|
||||||
|
|
||||||
// First update run to 'running' status
|
// First update run to 'running' status
|
||||||
|
|
|
||||||
|
|
@ -20,16 +20,26 @@ export function ScenarioViewer({ scenario, onSave, onClose }: ScenarioViewerProp
|
||||||
const [criteria, setCriteria] = useState(scenario.criteria || '');
|
const [criteria, setCriteria] = useState(scenario.criteria || '');
|
||||||
const [context, setContext] = useState(scenario.context || '');
|
const [context, setContext] = useState(scenario.context || '');
|
||||||
|
|
||||||
// Save changes whenever any field changes
|
// Replace the existing useEffect with this debounced version
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onSave({
|
const timeoutId = setTimeout(() => {
|
||||||
...scenario,
|
// Only save if any value has actually changed
|
||||||
name,
|
if (name !== scenario.name ||
|
||||||
description,
|
description !== scenario.description ||
|
||||||
criteria,
|
criteria !== scenario.criteria ||
|
||||||
context,
|
context !== scenario.context) {
|
||||||
});
|
onSave({
|
||||||
}, [name, description, criteria, context]);
|
...scenario,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
criteria,
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 500); // 500ms debounce
|
||||||
|
|
||||||
|
return () => clearTimeout(timeoutId);
|
||||||
|
}, [name, description, criteria, context, onSave, scenario]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue