- {/* Character Avatar or Create New Agent Icon */}
- {selectedCharacter ? (
-
- )}
-
{/* Character Info or Create New Agent */}
diff --git a/lib/hooks/use-streaming-message.ts b/lib/hooks/use-streaming-message.ts
index 9d420a06b..c275c23f2 100644
--- a/lib/hooks/use-streaming-message.ts
+++ b/lib/hooks/use-streaming-message.ts
@@ -177,6 +177,8 @@ export async function sendStreamingMessage({
const decoder = new TextDecoder();
let buffer = "";
+ let completeCalled = false;
+
try {
while (true) {
const { done, value } = await reader.read();
@@ -191,13 +193,20 @@ export async function sendStreamingMessage({
onChunk,
onReasoning,
onError,
- onComplete,
+ () => {
+ completeCalled = true;
+ onComplete?.();
+ },
);
} catch (err) {
console.error("[Stream] Error processing final buffer:", err);
onError?.("Stream ended unexpectedly");
}
}
+ // Always call onComplete when stream ends, if not already called
+ if (!completeCalled) {
+ onComplete?.();
+ }
break;
}
diff --git a/lib/privy-sync.ts b/lib/privy-sync.ts
index 196d8af62..fd9e29d52 100644
--- a/lib/privy-sync.ts
+++ b/lib/privy-sync.ts
@@ -22,7 +22,7 @@ import {
import type { UserWithOrganization } from "@/lib/types";
import { getRandomUserAvatar } from "@/lib/utils/default-user-avatar";
-const DEFAULT_INITIAL_CREDITS = 5.0;
+const DEFAULT_INITIAL_CREDITS = 1.0;
const getInitialCredits = (): number => {
const envValue = process.env.INITIAL_FREE_CREDITS;
if (envValue) {
diff --git a/lib/services/room-title.ts b/lib/services/room-title.ts
index 042f240d5..1a1107187 100644
--- a/lib/services/room-title.ts
+++ b/lib/services/room-title.ts
@@ -1,8 +1,10 @@
import { roomsRepository, memoriesRepository } from "@/db/repositories";
+import { generateText } from "ai";
+import { gateway } from "@ai-sdk/gateway";
import { logger } from "@/lib/utils/logger";
/**
- * Generate a title for a room based on the first user message.
+ * Generate an AI-powered title for a room based on the first user message.
* Only generates if room currently has default title ("New Chat").
*
* @param roomId - The room ID to generate title for
@@ -19,6 +21,7 @@ export async function generateRoomTitle(
}
if (room.name && room.name !== "New Chat") {
+ logger.info(`[RoomTitle] Room already has title: ${room.name}`);
return null;
}
@@ -45,25 +48,97 @@ export async function generateRoomTitle(
return null;
}
- // Create title from first user message
- const title = text
- .replace(/\n/g, " ") // Remove newlines
- .replace(/\s+/g, " ") // Collapse whitespace
- .trim()
- .substring(0, 40) // Limit to 40 chars
- .trim();
+ // Generate AI title
+ let title: string;
+
+ try {
+ const prompt = `Create a brief 3-5 word title summarizing this message topic. Output ONLY the title, no quotes or explanation.
- const finalTitle = text.length > 40 ? `${title}...` : title;
+Message: ${text.slice(0, 300)}
- if (!finalTitle || finalTitle.length < 3) {
- return null;
+Title:`;
+
+ logger.info(`[RoomTitle] Generating AI title for room ${roomId}`);
+
+ const result = await generateText({
+ model: gateway.languageModel("openai/gpt-4o-mini"),
+ prompt,
+ });
+
+ // Clean up the generated title
+ title = result.text
+ .trim()
+ .replace(/^["']|["']$/g, "") // Remove quotes
+ .replace(/^Title:\s*/i, "") // Remove "Title:" prefix if present
+ .replace(/[.!?]$/, "") // Remove trailing punctuation
+ .split("\n")[0] // Take only first line
+ .slice(0, 50); // Limit length
+
+ logger.info(`[RoomTitle] AI generated: "${title}"`);
+
+ // Validate title is reasonable
+ if (!title || title.length < 3 || title.length > 50) {
+ logger.warn(`[RoomTitle] Invalid AI title, using fallback`);
+ title = generateFallbackTitle(text);
+ }
+ } catch (error) {
+ logger.error(`[RoomTitle] AI generation failed:`, error);
+ title = generateFallbackTitle(text);
}
- await roomsRepository.update(roomId, { name: finalTitle });
+ await roomsRepository.update(roomId, { name: title });
- logger.info(
- `[RoomTitle] Generated title for room ${roomId}: "${finalTitle}"`,
- );
+ logger.info(`[RoomTitle] Set title for room ${roomId}: "${title}"`);
+
+ return title;
+}
+
+/**
+ * Generate a descriptive title from the user message when AI fails.
+ */
+function generateFallbackTitle(message: string): string {
+ const cleaned = message.trim().toLowerCase();
+
+ // Common greeting patterns -> generic titles
+ if (/^(hi|hello|hey|howdy|greetings|yo|sup)/i.test(cleaned)) {
+ return "New Conversation";
+ }
+
+ // Question patterns
+ if (/^(what|how|why|when|where|who|can|could|would|should|is|are|do|does)/i.test(cleaned)) {
+ const words = message.trim().split(/\s+/).slice(0, 6);
+ if (words.length >= 3) {
+ return capitalizeFirst(words.slice(0, 5).join(" "));
+ }
+ return "Question & Answer";
+ }
+
+ // Help/assist patterns
+ if (/^(help|assist|support|i need|please)/i.test(cleaned)) {
+ return "Help Request";
+ }
+
+ // Code/technical patterns
+ if (/^(code|write|create|build|make|implement|debug|fix)/i.test(cleaned)) {
+ return "Coding Assistance";
+ }
+
+ // Explain patterns
+ if (/^(explain|tell me|describe|what is|define)/i.test(cleaned)) {
+ return "Explanation Request";
+ }
+
+ // For other messages, extract first few meaningful words
+ const words = message.trim().split(/\s+/);
+ if (words.length <= 5) {
+ return capitalizeFirst(words.join(" ").replace(/[.!?]+$/, ""));
+ }
+
+ // Take first 5 words and capitalize
+ const title = words.slice(0, 5).join(" ");
+ return capitalizeFirst(title) + "...";
+}
- return finalTitle;
+function capitalizeFirst(str: string): string {
+ return str.charAt(0).toUpperCase() + str.slice(1);
}
diff --git a/lib/utils/character-names.ts b/lib/utils/character-names.ts
index 6e3c15380..e9d779ea0 100644
--- a/lib/utils/character-names.ts
+++ b/lib/utils/character-names.ts
@@ -64,12 +64,12 @@ export function generateDefaultCharacterName(): string {
}
/**
- * Create a new default character with random name and avatar.
+ * Create a new default character with blank fields for user to fill in.
*/
export function createDefaultCharacter(): ElizaCharacter {
- const name = generateDefaultCharacterName();
return {
- name,
+ name: "",
+ username: "",
bio: "",
system: "",
topics: [],
@@ -80,6 +80,5 @@ export function createDefaultCharacter(): ElizaCharacter {
secrets: {},
style: {},
templates: {},
- avatarUrl: generateDefaultAvatarUrl(name),
};
}