Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Creating Archetype Selector #2422

Closed
Closed
5 changes: 5 additions & 0 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Chat from "./routes/chat";
import Overview from "./routes/overview";
import Home from "./routes/home";
import useVersion from "./hooks/use-version";
import { ArchetypeSelector } from "./components/archetype-selector";

const queryClient = new QueryClient({
defaultOptions: {
Expand Down Expand Up @@ -40,6 +41,10 @@ function App() {
path="chat/:agentId"
element={<Chat />}
/>
<Route
path="settings/archetypes/:agentId"
element={<ArchetypeSelector />}
/>
<Route
path="settings/:agentId"
element={<Overview />}
Expand Down
43 changes: 29 additions & 14 deletions client/src/components/app-sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from "react";
import { useQuery } from "@tanstack/react-query";
import info from "@/lib/info.json";
import {
Expand Down Expand Up @@ -73,22 +74,36 @@ export function AppSidebar() {
<div>
{agents?.map(
(agent: { id: UUID; name: string }) => (
<SidebarMenuItem key={agent.id}>
<NavLink
to={`/chat/${agent.id}`}
<React.Fragment key={agent.id}>
<SidebarMenuItem>
<NavLink
to={`/chat/${agent.id}`}
>
<SidebarMenuButton
isActive={location.pathname.includes(
agent.id
)}
>
<User />
<span>
{agent.name}
</span>
</SidebarMenuButton>
</NavLink>
</SidebarMenuItem>
<SidebarMenuItem
key={`${agent.id}-archetype`}
>
<SidebarMenuButton
isActive={location.pathname.includes(
agent.id
)}
<NavLink
to={`/settings/archetypes/${agent.id}`}
>
<User />
<span>
{agent.name}
</span>
</SidebarMenuButton>
</NavLink>
</SidebarMenuItem>
<SidebarMenuButton>
<Cog /> Change
Agent's Archetype
</SidebarMenuButton>
</NavLink>
</SidebarMenuItem>
</React.Fragment>
)
)}
</div>
Expand Down
43 changes: 43 additions & 0 deletions client/src/components/archetype-selector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React, { useState } from "react";
import { ArchetypeName } from "../types/archetypes";
import { useParams } from "react-router";
import { type UUID } from "@elizaos/core";
import ArcheTypeButtons from "./ui/archetype/archetype-buttons";
import ArchetypeDisplay from "./ui/archetype/archetype-display";
import ArchetypeSelection from "./ui/archetype/archetype-selection";
import ArchetypeApplyStatus from "./ui/archetype/archetype-apply-status";
import ArchetypeNoAgent from "./ui/archetype/archetype-no-agent";

export const ArchetypeSelector: React.FC = () => {
const { agentId } = useParams<{ agentId: UUID }>();

if (!agentId) {
return <ArchetypeNoAgent />;
}

const [selectedArchetype, setSelectedArchetype] =
useState<ArchetypeName | null>(null);
const [applyStatus, setApplyStatus] = useState<string | null>(null);

return (
<div className="p-4">
<h2 className="text-lg font-bold mb-4">
Character Archetype Selector
</h2>

<ArchetypeSelection setSelectedArchetype={setSelectedArchetype} />

{selectedArchetype && (
<ArchetypeDisplay selectedArchetype={selectedArchetype} />
)}

<ArcheTypeButtons
agentId={agentId}
selectedArchetype={selectedArchetype}
setApplyStatus={setApplyStatus}
/>

<ArchetypeApplyStatus applyStatus={applyStatus} />
</div>
);
};
19 changes: 19 additions & 0 deletions client/src/components/ui/archetype/archetype-apply-status.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
type ArchetypeApplyStatusProps = {
applyStatus: string | null;
};

const ArchetypeApplyStatus = ({ applyStatus }: ArchetypeApplyStatusProps) => {
let message;
let containerClass;
if (applyStatus === "success") {
message = "Archetype applied successfully!";
containerClass = "text-green-600";
} else {
message = "Failed to apply archetype. Please try again";
containerClass = "text-red-600";
}

return <div className={`mt-2 ${containerClass}`}>{message}</div>;
};

export default ArchetypeApplyStatus;
85 changes: 85 additions & 0 deletions client/src/components/ui/archetype/archetype-buttons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { apiClient } from "@/lib/api";
import { ArchetypeName, archetypes } from "@/types/archetypes";
import { type UUID } from "@elizaos/core";
import { Button } from "../button";
import { useState } from "react";

type ArcheTypeButtonsProps = {
agentId: UUID;
selectedArchetype: ArchetypeName | null;
setApplyStatus: (status: string | null) => void;
};

const ArcheTypeButtons = ({
agentId,
selectedArchetype,
setApplyStatus,
}: ArcheTypeButtonsProps) => {
const [isApplying, setIsApplying] = useState(false);

const handleApply = async () => {
if (!selectedArchetype) return;

if (
!window.confirm(
`Are you sure you want to apply the ${selectedArchetype} archetype?`
)
)
return;

try {
setIsApplying(true);
await apiClient.applyArchetype(
agentId as UUID,
archetypes[selectedArchetype]
);
setApplyStatus("success");
} catch (error) {
console.error("Failed to apply archetype:", error);
setApplyStatus(
`Error: ${error instanceof Error ? error.message : "Unknown error"}`
);
} finally {
setIsApplying(false);
}
Comment on lines +30 to +44
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setApplyStatus('success') call appears to be in the wrong place. Since it's inside the try block but before the await, it will execute before knowing if the API call succeeded. Moving it after the await would ensure the success message only displays after a confirmed successful API response.

Spotted by Graphite Reviewer

Is this helpful? React 👍 or 👎 to let us know.


setTimeout(() => setApplyStatus(null), 3000);
};

const handleDownload = () => {
if (!selectedArchetype) return;

const blob = new Blob(
[JSON.stringify(archetypes[selectedArchetype], null, 2)],
{ type: "application/json" }
);
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = `${selectedArchetype}.json`;
link.click();
URL.revokeObjectURL(url);
};

return (
<div className="flex gap-4 mt-4">
<Button
onClick={handleDownload}
className="bg-blue-600 text-white rounded disabled:opacity-50"
disabled={!selectedArchetype}
>
Download Archetype
</Button>

<Button
onClick={handleApply}
className="bg-green-600 text-white rounded disabled:opacity-50"
disabled={!selectedArchetype || !agentId || isApplying}
>
{isApplying ? "Applying..." : "Apply Archetype"}
</Button>
</div>
);
};

export default ArcheTypeButtons;
16 changes: 16 additions & 0 deletions client/src/components/ui/archetype/archetype-display.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ArchetypeName, archetypes } from "@/types/archetypes";

type ArchetypeDisplayProps = {
selectedArchetype: ArchetypeName;
};

const ArchetypeDisplay = ({ selectedArchetype }: ArchetypeDisplayProps) => (
<div>
<h3 className="text-md font-semibold">Preview</h3>
<pre className="p-4 bg-gray-800 text-white rounded border overflow-auto">
{JSON.stringify(archetypes[selectedArchetype], null, 2)}
</pre>
</div>
);

export default ArchetypeDisplay;
7 changes: 7 additions & 0 deletions client/src/components/ui/archetype/archetype-no-agent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const ArchetypeNoAgent = () => (
<div className="p-4">
Agent ID not found, Archetype Selector not available.
</div>
);

export default ArchetypeNoAgent;
31 changes: 31 additions & 0 deletions client/src/components/ui/archetype/archetype-selection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ArchetypeName } from "@/types/archetypes";

type ArchetypeSelectionProps = {
setSelectedArchetype: (selectedArchetype: ArchetypeName) => void;
};

const ArchetypeSelection = ({
setSelectedArchetype,
}: ArchetypeSelectionProps) => (
<div className="mb-4">
<label htmlFor="archetype-select" className="block text-sm font-medium">
Select an Archetype
</label>
<select
id="archetype-select"
className="mt-2 p-2 border rounded"
onChange={(e) =>
setSelectedArchetype(e.target.value as ArchetypeName)
}
>
<option value="">-- Select --</option>
{Object.keys(ArchetypeName).map((key) => (
<option key={key} value={key}>
{key}
</option>
))}
</select>
</div>
);

export default ArchetypeSelection;
6 changes: 6 additions & 0 deletions client/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,10 @@ export const apiClient = {
body: formData,
});
},
applyArchetype: (agentId: string, archetype: Character) =>
fetcher({
url: `/agents/${agentId}/set`,
method: "POST",
body: archetype,
}),
};
Loading
Loading