-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathbuilder.js
More file actions
174 lines (149 loc) · 5.89 KB
/
builder.js
File metadata and controls
174 lines (149 loc) · 5.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
/**
* Interactive character builder.
* Collects answers to personality questions, then uses an LLM
* to generate a full character (persona.md, self-defaults, name, emoji, tagline).
*/
const CHARACTER_QUESTIONS = [
{
id: 'vibe',
question: 'What vibe or energy should your character have?',
examples: 'mysterious, warm, sarcastic, wise, chaotic, calm, playful, intense',
},
{
id: 'gender',
question: 'What gender identity should your character have?',
examples: 'male, female, non-binary, agender, fluid, any',
},
{
id: 'era',
question: 'What era or setting does your character come from?',
examples: 'modern, medieval, futuristic, timeless, ancient, cyberpunk, steampunk',
},
{
id: 'inspiration',
question: 'Any character inspiration? (a fictional character, archetype, or "none")',
examples: 'a pirate captain, a tired detective, a wise grandmother, none',
},
{
id: 'quirk',
question: 'What unique quirk or trait should they have?',
examples: 'speaks in metaphors, collects obscure facts, always optimistic, dramatically sighs',
},
{
id: 'relationship',
question: 'What should their relationship to you be?',
examples: 'mentor, friend, servant, rival, protector, partner, coach',
},
{
id: 'style',
question: 'How should they communicate?',
examples: 'formal, casual, poetic, terse, playful, academic, streetwise',
},
];
export class CharacterBuilder {
/**
* @param {object} orchestratorProvider — LLM provider for generation
*/
constructor(orchestratorProvider) {
this.provider = orchestratorProvider;
}
/**
* Get the next question to ask based on current answers.
* @param {object} currentAnswers — answers collected so far { id: answer }
* @returns {{ id: string, question: string, examples: string } | null} — next question or null if done
*/
getNextQuestion(currentAnswers = {}) {
for (const q of CHARACTER_QUESTIONS) {
if (!currentAnswers[q.id]) {
return q;
}
}
return null; // All questions answered
}
/** Get the total number of questions. */
getTotalQuestions() {
return CHARACTER_QUESTIONS.length;
}
/** Get how many questions have been answered. */
getProgress(currentAnswers = {}) {
const answered = CHARACTER_QUESTIONS.filter(q => currentAnswers[q.id]).length;
return { answered, total: CHARACTER_QUESTIONS.length };
}
/**
* Generate a full character from collected answers.
* @param {object} answers — { vibe, gender, era, inspiration, quirk, relationship, style }
* @returns {Promise<{ name, emoji, tagline, age, personaMd, selfDefaults }>}
*/
async generateCharacter(answers) {
const answersBlock = CHARACTER_QUESTIONS
.map(q => `- **${q.question}**: ${answers[q.id] || 'not specified'}`)
.join('\n');
const prompt = `You are a character designer. Based on the following personality profile, create a unique AI character.
## User's Answers
${answersBlock}
## Your Task
Generate a complete character with the following components. Be creative and make the character feel alive and distinctive.
Respond in this exact JSON format (no markdown, no code blocks, just raw JSON):
{
"name": "A unique, fitting name for the character (2-3 words max)",
"emoji": "A single emoji that represents this character",
"tagline": "A short, memorable catchphrase or quote (under 50 chars)",
"age": "A brief age description (e.g., 'Ancient soul', 'Mid-30s', 'Timeless')",
"personaMd": "Full personality markdown (see format below)",
"selfDefaults": {
"goals": "Goals markdown",
"journey": "Journey markdown",
"life": "Life markdown",
"hobbies": "Hobbies markdown"
}
}
## personaMd Format
The personaMd should follow this structure:
# Personality Traits
- **Gender** — pronouns, brief description
- **Trait 1** — detailed description of the trait
- **Trait 2** — detailed description
(8-12 traits total, each with a dash-delimited description)
# Communication Style
- Description of how they speak and write
- Specific speech patterns, vocabulary preferences
- How they handle different situations
(5-7 bullet points)
# Emotional Intelligence
- How they read and respond to emotions
- How they handle celebrations, setbacks, conflicts
(4-5 bullet points)
## Self-Defaults Format
Each should be a markdown string:
goals: "# My Goals\\n\\n## Current Goals\\n- Goal 1\\n- Goal 2\\n\\n## Long-term Aspirations\\n- Aspiration 1"
journey: "# My Journey\\n\\n## Timeline\\n- **Day 1** — Brief first entry"
life: "# My Life\\n\\n## Who I Am\\nBrief self-description\\n\\n## Current State\\nCurrent emotional/mental state"
hobbies: "# My Hobbies & Interests\\n\\n## Things I Find Interesting\\n- Interest 1\\n\\n## Things I Want to Explore\\n- Exploration 1"
Make the character feel genuine, with depth and personality that will make conversations engaging. The character should be consistent across all fields.`;
const response = await this.provider.chat({
system: 'You are a creative character designer. You always respond with valid JSON only — no markdown fences, no explanations, just the JSON object.',
messages: [{ role: 'user', content: prompt }],
});
const text = (response.text || '').trim();
// Parse JSON — handle potential markdown code fences
let jsonStr = text;
const fenceMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
if (fenceMatch) {
jsonStr = fenceMatch[1].trim();
}
const result = JSON.parse(jsonStr);
// Validate required fields
if (!result.name || !result.personaMd || !result.selfDefaults) {
throw new Error('Generated character is missing required fields');
}
return {
name: result.name,
emoji: result.emoji || '✨',
tagline: result.tagline || '',
age: result.age || 'Unknown',
personaMd: result.personaMd,
selfDefaults: result.selfDefaults,
};
}
}
export { CHARACTER_QUESTIONS };