Remove old scenarios files

This commit is contained in:
akhisud3195 2025-02-14 17:05:46 +05:30
parent 2f7688b1a6
commit 6f022e3f1b
7 changed files with 35 additions and 426 deletions

View file

@ -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,
});
}

View file

@ -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);

View file

@ -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>
); );

View file

@ -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>
);
}

View file

@ -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>;
}

View file

@ -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

View file

@ -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>