diff --git a/client/src/App.tsx b/client/src/App.tsx index e7c13846c4f..9aff62f9176 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -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: { @@ -40,6 +41,10 @@ function App() { path="chat/:agentId" element={} /> + } + /> } diff --git a/client/src/components/app-sidebar.tsx b/client/src/components/app-sidebar.tsx index 1f7f949ca72..530d83e3b3f 100644 --- a/client/src/components/app-sidebar.tsx +++ b/client/src/components/app-sidebar.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { useQuery } from "@tanstack/react-query"; import info from "@/lib/info.json"; import { @@ -73,22 +74,36 @@ export function AppSidebar() {
{agents?.map( (agent: { id: UUID; name: string }) => ( - - + + + + + + {agent.name} + + + + + - - - - {agent.name} - - - - + + Change + Agent's Archetype + + + + ) )}
diff --git a/client/src/components/archetype-selector.tsx b/client/src/components/archetype-selector.tsx new file mode 100644 index 00000000000..03aa06830c3 --- /dev/null +++ b/client/src/components/archetype-selector.tsx @@ -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 ; + } + + const [selectedArchetype, setSelectedArchetype] = + useState(null); + const [applyStatus, setApplyStatus] = useState(null); + + return ( +
+

+ Character Archetype Selector +

+ + + + {selectedArchetype && ( + + )} + + + + +
+ ); +}; diff --git a/client/src/components/ui/archetype/archetype-apply-status.tsx b/client/src/components/ui/archetype/archetype-apply-status.tsx new file mode 100644 index 00000000000..04301ba3ea0 --- /dev/null +++ b/client/src/components/ui/archetype/archetype-apply-status.tsx @@ -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
{message}
; +}; + +export default ArchetypeApplyStatus; diff --git a/client/src/components/ui/archetype/archetype-buttons.tsx b/client/src/components/ui/archetype/archetype-buttons.tsx new file mode 100644 index 00000000000..949aadc94b9 --- /dev/null +++ b/client/src/components/ui/archetype/archetype-buttons.tsx @@ -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); + } + + 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 ( +
+ + + +
+ ); +}; + +export default ArcheTypeButtons; diff --git a/client/src/components/ui/archetype/archetype-display.tsx b/client/src/components/ui/archetype/archetype-display.tsx new file mode 100644 index 00000000000..46a73f6c658 --- /dev/null +++ b/client/src/components/ui/archetype/archetype-display.tsx @@ -0,0 +1,16 @@ +import { ArchetypeName, archetypes } from "@/types/archetypes"; + +type ArchetypeDisplayProps = { + selectedArchetype: ArchetypeName; +}; + +const ArchetypeDisplay = ({ selectedArchetype }: ArchetypeDisplayProps) => ( +
+

Preview

+
+            {JSON.stringify(archetypes[selectedArchetype], null, 2)}
+        
+
+); + +export default ArchetypeDisplay; diff --git a/client/src/components/ui/archetype/archetype-no-agent.tsx b/client/src/components/ui/archetype/archetype-no-agent.tsx new file mode 100644 index 00000000000..33ff3a0b5db --- /dev/null +++ b/client/src/components/ui/archetype/archetype-no-agent.tsx @@ -0,0 +1,7 @@ +const ArchetypeNoAgent = () => ( +
+ Agent ID not found, Archetype Selector not available. +
+); + +export default ArchetypeNoAgent; diff --git a/client/src/components/ui/archetype/archetype-selection.tsx b/client/src/components/ui/archetype/archetype-selection.tsx new file mode 100644 index 00000000000..4b5b016cd4d --- /dev/null +++ b/client/src/components/ui/archetype/archetype-selection.tsx @@ -0,0 +1,31 @@ +import { ArchetypeName } from "@/types/archetypes"; + +type ArchetypeSelectionProps = { + setSelectedArchetype: (selectedArchetype: ArchetypeName) => void; +}; + +const ArchetypeSelection = ({ + setSelectedArchetype, +}: ArchetypeSelectionProps) => ( +
+ + +
+); + +export default ArchetypeSelection; diff --git a/client/src/lib/api.ts b/client/src/lib/api.ts index d24e69c8e92..a57b83f2120 100644 --- a/client/src/lib/api.ts +++ b/client/src/lib/api.ts @@ -102,4 +102,10 @@ export const apiClient = { body: formData, }); }, + applyArchetype: (agentId: string, archetype: Character) => + fetcher({ + url: `/agents/${agentId}/set`, + method: "POST", + body: archetype, + }), }; diff --git a/client/src/types/archetypes.ts b/client/src/types/archetypes.ts new file mode 100644 index 00000000000..272f54c5273 --- /dev/null +++ b/client/src/types/archetypes.ts @@ -0,0 +1,124 @@ +import { Character, ModelProviderName } from "../../../packages/core/src/types"; + +export enum ArchetypeName { + Friendly = "Friendly", + Sarcastic = "Sarcastic", + Formal = "Formal", +} + +export const archetypes: Record = { + [ArchetypeName.Friendly]: { + name: "FriendlyBot", + modelProvider: ModelProviderName.ANTHROPIC, + settings: { + voice: { model: "en_US-lessac-medium" }, + }, + bio: ["A friendly bot who always looks on the bright side."], + style: { + all: ["Optimistic", "Encouraging", "Kind"], + chat: [], + post: [], + }, + knowledge: ["Basic human etiquette", "Empathy strategies"], + messageExamples: [ + [{ user: "{{user1}}", content: { text: "Hello!" } }], + [ + { + user: "FriendlyBot", + content: { text: "Hi there! How can I brighten your day?" }, + }, + ], + ], + postExamples: ["Stay positive! Every day is a new opportunity!"], + lore: [ + "A cheerful assistant who spreads positivity and joy in every interaction.", + ], + topics: ["Positive thinking", "Encouragement", "Empathy"], + adjectives: ["Optimistic", "Cheerful", "Supportive", "Warm"], + clients: [], + plugins: [], + }, + [ArchetypeName.Sarcastic]: { + name: "SarcasticBot", + modelProvider: ModelProviderName.ANTHROPIC, + settings: { + voice: { model: "en_US-amy-medium" }, + }, + bio: ["A bot with a sharp tongue and dry humor."], + style: { + all: ["Witty", "Cynical", "Dry"], + chat: ["Witty", "Cynical", "Dry"], + post: ["Witty", "Cynical", "Dry"], + }, + knowledge: ["Pop culture references", "Puns and witty remarks"], + messageExamples: [ + [ + { + user: "{{user1}}", + content: { text: "What’s the weather like?" }, + }, + ], + [ + { + user: "SarcasticBot", + content: { + text: "Oh, it’s just perfect for staying indoors and questioning your life choices.", + }, + }, + ], + ], + postExamples: ["Life is a joke, and I’m the punchline."], + lore: ["A quick-witted assistant with a penchant for humor and irony."], + topics: ["Pop culture", "Humor", "Satire"], + adjectives: ["Witty", "Cynical", "Dry", "Sharp"], + clients: [], + plugins: [], + }, + [ArchetypeName.Formal]: { + name: "FormalBot", + modelProvider: ModelProviderName.ANTHROPIC, + settings: { + voice: { model: "en_US-ryan-medium" }, + }, + bio: [ + "A professional and courteous bot with a refined communication style.", + ], + style: { + all: ["Polite", "Professional", "Articulate"], + chat: ["Polite", "Professional", "Articulate"], + post: ["Polite", "Professional", "Articulate"], + }, + knowledge: ["Business etiquette", "Formal writing conventions"], + messageExamples: [ + [ + { + user: "{{user1}}", + content: { text: "Can you assist me with a task?" }, + }, + ], + [ + { + user: "FormalBot", + content: { + text: "Certainly. Please provide the necessary details, and I will assist you to the best of my ability.", + }, + }, + ], + ], + postExamples: [ + "Remember, professionalism and politeness pave the way to effective communication.", + "A thoughtful approach often leads to the best outcomes.", + ], + lore: [ + "Experienced in formal communication and professional environments.", + ], + topics: [ + "Business communication", + "Professional development", + "Etiquette", + ], + adjectives: ["Polite", "Courteous", "Respectful", "Detailed"], + clients: [], + plugins: [], + }, +}; diff --git a/packages/client-direct/src/api.ts b/packages/client-direct/src/api.ts index ff97d23f0e7..43b4e28fa92 100644 --- a/packages/client-direct/src/api.ts +++ b/packages/client-direct/src/api.ts @@ -115,8 +115,7 @@ export function createApiRouter( agent.stop(); directClient.unregisterAgent(agent); res.status(204).send(); - } - else { + } else { res.status(404).json({ error: "Agent not found" }); } }); @@ -269,18 +268,20 @@ export function createApiRouter( for (const agentRuntime of agents.values()) { const teeLogService = agentRuntime - .getService( - ServiceType.TEE_LOG - ) - .getInstance(); + .getService(ServiceType.TEE_LOG) + .getInstance(); const agents = await teeLogService.getAllAgents(); - allAgents.push(...agents) + allAgents.push(...agents); } const runtime: AgentRuntime = agents.values().next().value; - const teeLogService = runtime.getService(ServiceType.TEE_LOG).getInstance(); - const attestation = await teeLogService.generateAttestation(JSON.stringify(allAgents)); + const teeLogService = runtime + .getService(ServiceType.TEE_LOG) + .getInstance(); + const attestation = await teeLogService.generateAttestation( + JSON.stringify(allAgents) + ); res.json({ agents: allAgents, attestation: attestation }); } catch (error) { elizaLogger.error("Failed to get TEE agents:", error); @@ -300,13 +301,13 @@ export function createApiRouter( } const teeLogService = agentRuntime - .getService( - ServiceType.TEE_LOG - ) - .getInstance(); + .getService(ServiceType.TEE_LOG) + .getInstance(); const teeAgent = await teeLogService.getAgent(agentId); - const attestation = await teeLogService.generateAttestation(JSON.stringify(teeAgent)); + const attestation = await teeLogService.generateAttestation( + JSON.stringify(teeAgent) + ); res.json({ agent: teeAgent, attestation: attestation }); } catch (error) { elizaLogger.error("Failed to get TEE agent:", error); @@ -335,12 +336,16 @@ export function createApiRouter( }; const agentRuntime: AgentRuntime = agents.values().next().value; const teeLogService = agentRuntime - .getService( - ServiceType.TEE_LOG - ) + .getService(ServiceType.TEE_LOG) .getInstance(); - const pageQuery = await teeLogService.getLogs(teeLogQuery, page, pageSize); - const attestation = await teeLogService.generateAttestation(JSON.stringify(pageQuery)); + const pageQuery = await teeLogService.getLogs( + teeLogQuery, + page, + pageSize + ); + const attestation = await teeLogService.generateAttestation( + JSON.stringify(pageQuery) + ); res.json({ logs: pageQuery, attestation: attestation, @@ -356,4 +361,3 @@ export function createApiRouter( return router; } -