diff --git a/packages/client/src/components/character-form.tsx b/packages/client/src/components/character-form.tsx
index b13aff7b9b1..37b07c00ca9 100644
--- a/packages/client/src/components/character-form.tsx
+++ b/packages/client/src/components/character-form.tsx
@@ -3,142 +3,126 @@ import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
-import { Separator } from "@/components/ui/separator";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Textarea } from "@/components/ui/textarea";
import { useToast } from "@/hooks/use-toast";
import type { Character } from "@elizaos/core";
import React, { useState, type FormEvent, type ReactNode } from "react";
-import { useNavigate } from "react-router-dom";
-import { useQueryClient } from "@tanstack/react-query";
-// Error Boundary Component
-export class ErrorBoundary extends React.Component<{ children: ReactNode }, { hasError: boolean }> {
- constructor(props: { children: ReactNode }) {
- super(props);
- this.state = { hasError: false };
- }
-
- static getDerivedStateFromError() {
- return { hasError: true };
- }
-
- render() {
- if (this.state.hasError) {
- return (
-
-
Something went wrong
-
Please try refreshing the page.
-
- );
- }
-
- return this.props.children;
- }
-}
-
-type NestedObject = {
- [key: string]: string | number | boolean | NestedObject;
-};
-
-type UpdateArrayPath =
- | "bio"
- | "topics"
- | "adjectives"
- | "plugins"
- | "style.all"
- | "style.chat"
- | "style.post";
+type FieldType = "text" | "textarea" | "number" | "checkbox" | "select";
type InputField = {
title: string;
name: string;
description?: string;
getValue: (char: Character) => string;
+ fieldType: FieldType
};
type ArrayField = {
title: string;
description?: string;
- path: UpdateArrayPath;
+ path: string;
getData: (char: Character) => string[];
};
-type CheckboxField = {
- name: string;
- label: string;
- description?: string;
- getValue: (char: Character) => boolean;
-};
-
-const TEXT_FIELDS: InputField[] = [
- {
- title: "Name",
- name: "name",
- description: "The display name of your character",
- getValue: (char) => char.name || '',
- },
- {
- title: "Username",
- name: "username",
- description: "Unique identifier for your character",
- getValue: (char) => char.username || '',
- },
- {
- title: "System",
- name: "system",
- description: "System prompt for character behavior",
- getValue: (char) => char.system || '',
- },
- {
- title: "Voice Model",
- name: "settings.voice.model",
- description: "Voice model used for speech synthesis",
- getValue: (char) => char.settings?.voice?.model || '',
- },
-];
+enum SECTION_TYPE {
+ INPUT = "input",
+ ARRAY = "array"
+}
-const ARRAY_FIELDS: ArrayField[] = [
+const CHARACTER_FORM_SCHEMA = [
{
- title: "Bio",
- description: "Key information about your character",
- path: "bio",
- getData: (char) => Array.isArray(char.bio) ? char.bio : [],
+ sectionTitle: "Basic Info",
+ sectionValue: "basic",
+ sectionType: SECTION_TYPE.INPUT,
+ fields: [
+ {
+ title: "Name",
+ name: "name",
+ description: "The display name of your character",
+ fieldType: "text",
+ getValue: (char) => char.name || '',
+ },
+ {
+ title: "Username",
+ name: "username",
+ description: "Unique identifier for your character",
+ fieldType: "text",
+ getValue: (char) => char.username || '',
+ },
+ {
+ title: "System",
+ name: "system",
+ description: "System prompt for character behavior",
+ fieldType: "textarea",
+ getValue: (char) => char.system || '',
+ },
+ {
+ title: "Voice Model",
+ name: "settings.voice.model",
+ description: "Voice model used for speech synthesis",
+ fieldType: "text",
+ getValue: (char) => char.settings?.voice?.model || '',
+ },
+ ] as InputField[]
},
{
- title: "Topics",
- description: "Topics your character is knowledgeable about",
- path: "topics",
- getData: (char) => char.topics || [],
+ sectionTitle: "Content",
+ sectionValue: "content",
+ sectionType: SECTION_TYPE.ARRAY,
+ fields: [
+ {
+ title: "Bio",
+ description: "Key information about your character",
+ path: "bio",
+ getData: (char) => Array.isArray(char.bio) ? char.bio : [],
+ },
+ {
+ title: "Topics",
+ description: "Topics your character is knowledgeable about",
+ path: "topics",
+ getData: (char) => char.topics || [],
+ },
+ {
+ title: "Adjectives",
+ description: "Words that describe your character's personality",
+ path: "adjectives",
+ getData: (char) => char.adjectives || [],
+ },
+ ] as ArrayField[]
},
{
- title: "Adjectives",
- description: "Words that describe your character's personality",
- path: "adjectives",
- getData: (char) => char.adjectives || [],
- },
-];
+ sectionTitle: "Style",
+ sectionValue: "style",
+ sectionType: SECTION_TYPE.ARRAY,
+ fields: [
+ {
+ title: "All",
+ description: "Style rules applied to all interactions",
+ path: "style.all",
+ getData: (char) => char.style?.all || [],
+ },
+ {
+ title: "Chat",
+ description: "Style rules for chat interactions",
+ path: "style.chat",
+ getData: (char) => char.style?.chat || [],
+ },
+ {
+ title: "Post",
+ description: "Style rules for social media posts",
+ path: "style.post",
+ getData: (char) => char.style?.post || [],
+ },
+ ] as ArrayField[]
+ }
+]
-const STYLE_FIELDS: ArrayField[] = [
- {
- title: "All",
- description: "Style rules applied to all interactions",
- path: "style.all",
- getData: (char) => char.style?.all || [],
- },
- {
- title: "Chat",
- description: "Style rules for chat interactions",
- path: "style.chat",
- getData: (char) => char.style?.chat || [],
- },
- {
- title: "Post",
- description: "Style rules for social media posts",
- path: "style.post",
- getData: (char) => char.style?.post || [],
- },
-];
+type customComponent = {
+ name: string,
+ component: ReactNode
+}
export type CharacterFormProps = {
character: Character;
@@ -148,12 +132,10 @@ export type CharacterFormProps = {
onDelete?: () => Promise;
onCancel?: () => void;
onReset?: () => void;
- showPlugins?: boolean;
- showSettings?: boolean;
submitButtonText?: string;
deleteButtonText?: string;
isAgent?: boolean;
- settingsContent?: ReactNode;
+ customComponents?: customComponent[];
};
export default function CharacterForm({
@@ -164,16 +146,11 @@ export default function CharacterForm({
onDelete,
onCancel,
onReset,
- showPlugins = false,
- showSettings = false,
submitButtonText = "Save Changes",
deleteButtonText = "Delete",
- isAgent = false,
- settingsContent,
+ customComponents = []
}: CharacterFormProps) {
const { toast } = useToast();
- const navigate = useNavigate();
- const queryClient = useQueryClient();
const [characterValue, setCharacterValue] = useState(character);
const [isSubmitting, setIsSubmitting] = useState(false);
@@ -187,13 +164,13 @@ export default function CharacterForm({
const parts = name.split('.');
setCharacterValue(prev => {
const newValue = { ...prev };
- let current = newValue as unknown as NestedObject;
+ let current: Record = newValue;
for (let i = 0; i < parts.length - 1; i++) {
if (!current[parts[i]]) {
current[parts[i]] = {};
}
- current = current[parts[i]] as NestedObject;
+ current = current[parts[i]];
}
current[parts[parts.length - 1]] = type === 'checkbox' ? checked : value;
@@ -207,27 +184,27 @@ export default function CharacterForm({
}
};
- const updateArray = (path: UpdateArrayPath, newData: string[]) => {
+ const updateArray = (path: string, newData: string[]) => {
setCharacterValue(prev => {
const newValue = { ...prev };
-
- if (path.includes('.')) {
- const [parent, child] = path.split('.') as ["style", "all" | "chat" | "post"];
- return {
- ...newValue,
- [parent]: {
- ...(newValue[parent] || {}),
- [child]: newData
- }
- };
+ const keys = path.split(".");
+ let current: any = newValue;
+
+ for (let i = 0; i < keys.length - 1; i++) {
+ const key = keys[i];
+
+ if (!current[key] || typeof current[key] !== "object") {
+ current[key] = {}; // Ensure path exists
+ }
+ current = current[key];
}
-
- return {
- ...newValue,
- [path]: newData
- } as Character;
+
+ current[keys[keys.length - 1]] = newData; // Update array
+
+ return newValue;
});
};
+
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
@@ -264,6 +241,48 @@ export default function CharacterForm({
}
};
+
+ const renderInputField = (field: InputField) => (
+
+
+ {field.description &&
{field.description}
}
+
+ {field.fieldType === "textarea" ? (
+
+ ) : field.fieldType === "checkbox" ? (
+
)[field.name] === "true"}
+ onChange={handleChange}
+ />
+ ) : (
+
+ )}
+
+ );
+
+ const renderArrayField = (field: ArrayField) => (
+
+
+ {field.description &&
{field.description}
}
+
updateArray(field.path, newData)} />
+
+ );
+
return (
@@ -275,85 +294,30 @@ export default function CharacterForm({
- {onReset && (
-
- )}
+