continue scaffolding and refine the basic foundation
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
import { agents } from "~~/db/schema";
|
||||
import { protectRoute } from "~~/server/utils/auth";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
await protectRoute(event);
|
||||
|
||||
const db = useDrizzle();
|
||||
const userId = event.context.user.id;
|
||||
|
||||
const rows = await db.select().from(agents);
|
||||
// Only return agents for the authenticated user
|
||||
const rows = await db.select().from(agents).where(eq(agents.userId, userId));
|
||||
return rows;
|
||||
});
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { protectRoute } from '~~/server/utils/auth';
|
||||
import { registerPendingGeneration } from '~~/server/utils/generation';
|
||||
import { createPendingGeneration } from '~~/server/utils/generation';
|
||||
import type { GenerateRequestBody } from '~~/server/types/chat';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
await protectRoute(event);
|
||||
|
||||
const body = await readBody(event) as GenerateRequestBody;
|
||||
const { topicId, messages } = body;
|
||||
const { topicId, messages, regeneratesFrom } = body;
|
||||
|
||||
if (!topicId || !messages) {
|
||||
throw createError({
|
||||
@@ -22,12 +22,19 @@ export default defineEventHandler(async (event) => {
|
||||
});
|
||||
}
|
||||
|
||||
const generationId = `gen_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
||||
try {
|
||||
const generationId = await createPendingGeneration(event.context.user.id, topicId, messages, regeneratesFrom);
|
||||
|
||||
registerPendingGeneration(event.context.user.id, generationId, topicId, messages);
|
||||
|
||||
return {
|
||||
generationId,
|
||||
status: 'pending'
|
||||
};
|
||||
return {
|
||||
generationId,
|
||||
status: 'pending',
|
||||
regeneratesFrom
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to create generation:', error);
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Failed to create generation'
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { protectRoute } from '~~/server/utils/auth';
|
||||
import { getPendingGeneration, getActiveGeneration, isGenerationActive } from '~~/server/utils/generation';
|
||||
import type { GenerationStatus } from '~~/server/types/chat';
|
||||
import { getGenerationStatus } from '~~/server/utils/generation';
|
||||
import type { GenerationStatusResponse } from '~~/server/types/chat';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
await protectRoute(event);
|
||||
@@ -14,39 +14,30 @@ export default defineEventHandler(async (event) => {
|
||||
});
|
||||
}
|
||||
|
||||
const pendingGeneration = getPendingGeneration(generationId);
|
||||
const isActive = isGenerationActive(generationId);
|
||||
const activeGeneration = getActiveGeneration(generationId);
|
||||
const generation = await getGenerationStatus(generationId);
|
||||
|
||||
if (!pendingGeneration && !activeGeneration) {
|
||||
if (!generation) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'Generation not found'
|
||||
});
|
||||
}
|
||||
|
||||
if (pendingGeneration) {
|
||||
const status: GenerationStatus = {
|
||||
generationId,
|
||||
status: 'pending',
|
||||
topicId: pendingGeneration.topicId
|
||||
};
|
||||
return status;
|
||||
// Verify ownership
|
||||
if (generation.userId !== event.context.user.id) {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: 'Unauthorized'
|
||||
});
|
||||
}
|
||||
|
||||
if (isActive && activeGeneration) {
|
||||
const status: GenerationStatus = {
|
||||
generationId,
|
||||
status: 'active',
|
||||
content: activeGeneration.content,
|
||||
topicId: activeGeneration.topicId
|
||||
};
|
||||
return status;
|
||||
}
|
||||
|
||||
const status: GenerationStatus = {
|
||||
const status: GenerationStatusResponse = {
|
||||
generationId,
|
||||
status: 'completed'
|
||||
status: generation.status as any,
|
||||
topicId: generation.topicId,
|
||||
content: generation.content,
|
||||
error: generation.error || undefined
|
||||
};
|
||||
|
||||
return status;
|
||||
});
|
||||
@@ -1,8 +1,9 @@
|
||||
import { protectRoute } from '~~/server/utils/auth';
|
||||
import { getPendingGeneration, startGeneration, addClientToGeneration, removeClientFromGeneration, sendToClient } from '~~/server/utils/generation';
|
||||
import { startGeneration, addClientToGeneration, removeClientFromGeneration, sendToClient, isGenerationStreaming, getGenerationStatus } from '~~/server/utils/generation';
|
||||
import { eventHandler, setHeader, setResponseStatus } from 'h3';
|
||||
import { generations, messages } from '~~/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { useDrizzle } from '~~/server/utils/drizzle';
|
||||
import { generations, messages } from '~~/db/schema';
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
await protectRoute(event);
|
||||
@@ -16,12 +17,20 @@ export default eventHandler(async (event) => {
|
||||
});
|
||||
}
|
||||
|
||||
const pendingGeneration = getPendingGeneration(generationId);
|
||||
|
||||
if (pendingGeneration && pendingGeneration.expired) {
|
||||
// Fetch generation from database
|
||||
const generation = await getGenerationStatus(generationId);
|
||||
if (!generation) {
|
||||
throw createError({
|
||||
statusCode: 410,
|
||||
statusMessage: 'Generation expired - no client connected within 60 seconds'
|
||||
statusCode: 404,
|
||||
statusMessage: 'Generation not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Verify ownership
|
||||
if (generation.userId !== event.context.user.id) {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: 'Unauthorized'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -32,36 +41,81 @@ export default eventHandler(async (event) => {
|
||||
|
||||
setResponseStatus(event, 200);
|
||||
|
||||
const shouldStartGeneration = pendingGeneration;
|
||||
|
||||
try {
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
addClientToGeneration(generationId, controller);
|
||||
try {
|
||||
// If generation is already completed, send the completed message
|
||||
if (generation.status === 'completed' && generation.messageId) {
|
||||
const db = useDrizzle();
|
||||
const [message] = await db
|
||||
.select()
|
||||
.from(messages)
|
||||
.where(eq(messages.id, generation.messageId));
|
||||
|
||||
if (shouldStartGeneration) {
|
||||
startGeneration(generationId, controller);
|
||||
} else {
|
||||
const generation = await useDrizzle().select().from(generations).where(eq(generations.id, getRouterParam(event, 'id')!))
|
||||
if (!generation) throw createError({ statusCode: 404, statusMessage: 'Generation not found' });
|
||||
const message = await useDrizzle().select().from(messages).where(eq(messages.id, generation[0].messageId!))
|
||||
if (!message) throw createError({ statusCode: 404, statusMessage: 'Message not found' });
|
||||
if (message) {
|
||||
sendToClient(controller, {
|
||||
type: 'complete',
|
||||
data: message
|
||||
});
|
||||
}
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// If generation failed, send the error
|
||||
if (generation.status === 'failed') {
|
||||
sendToClient(controller, {
|
||||
type: 'error',
|
||||
data: { error: generation.error || 'Generation failed' }
|
||||
});
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// If already streaming, just add this client
|
||||
if (isGenerationStreaming(generationId)) {
|
||||
addClientToGeneration(generationId, controller);
|
||||
} else {
|
||||
// Start generation if in pending status
|
||||
if (generation.status === 'pending') {
|
||||
// Fetch the original messages context (stored in topic messages)
|
||||
const db = useDrizzle();
|
||||
const topicMessages = await db
|
||||
.select()
|
||||
.from(messages)
|
||||
.where(eq(messages.topicId, generation.topicId));
|
||||
|
||||
const chatMessages = topicMessages.map(m => ({
|
||||
type: m.isUser ? 'user' as const : ('agent' as const),
|
||||
message: m.content
|
||||
}));
|
||||
|
||||
addClientToGeneration(generationId, controller);
|
||||
await startGeneration(generationId, generation.userId, generation.topicId, chatMessages, controller);
|
||||
}
|
||||
}
|
||||
|
||||
event.node.req.on('close', () => {
|
||||
removeClientFromGeneration(generationId, controller);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Stream start error:', error);
|
||||
sendToClient(controller, {
|
||||
type: 'complete',
|
||||
data: message[0].content
|
||||
type: 'error',
|
||||
data: { error: 'Stream initialization failed' }
|
||||
});
|
||||
controller.close();
|
||||
}
|
||||
|
||||
event.node.req.on('close', () => {
|
||||
removeClientFromGeneration(generationId, controller);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return sendStream(event, stream);
|
||||
} catch (error) {
|
||||
// assume it failed because the generation is complete so try to send the message
|
||||
console.error('Stream error:', error);
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Stream error'
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,13 +1,26 @@
|
||||
import { and, desc, eq } from "drizzle-orm";
|
||||
import { and, asc, eq } from "drizzle-orm";
|
||||
import { messages, topics } from "~~/db/schema";
|
||||
import { protectRoute } from "~~/server/utils/auth";
|
||||
import type { Message, Topic } from '~~/types'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
await protectRoute(event);
|
||||
|
||||
const db = useDrizzle();
|
||||
const userId = event.context.user.id;
|
||||
const topicId = getRouterParam(event, 'id');
|
||||
|
||||
const rows = await db.select().from(topics).where(and(eq(topics.userId, event.context.user.id), eq(topics.id, getRouterParam(event, 'id')!)));
|
||||
if (!topicId) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Topic ID is required'
|
||||
});
|
||||
}
|
||||
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(topics)
|
||||
.where(and(eq(topics.userId, userId), eq(topics.id, topicId)));
|
||||
|
||||
if (rows.length === 0) {
|
||||
throw createError({
|
||||
@@ -17,7 +30,13 @@ export default defineEventHandler(async (event) => {
|
||||
}
|
||||
|
||||
const topic = rows[0] as Topic & { messages: Message[] };
|
||||
topic.messages = await db.select().from(messages).where(eq(messages.topicId, topic.id)).orderBy(desc(messages.createdAt));
|
||||
|
||||
// Fetch messages for this topic, ordered chronologically
|
||||
topic.messages = await db
|
||||
.select()
|
||||
.from(messages)
|
||||
.where(eq(messages.topicId, topic.id))
|
||||
.orderBy(asc(messages.createdAt));
|
||||
|
||||
return topic;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { messages, topics } from "~~/db/schema";
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
await protectRoute(event);
|
||||
|
||||
const db = useDrizzle();
|
||||
|
||||
const { id } = event.context.params!;
|
||||
|
||||
const [topic] = await db.select().from(topics).where(eq(topics.id, id));
|
||||
if (topic === undefined || topic.userId !== event.context.user.id) {
|
||||
throw createError({ statusCode: 404, statusMessage: 'Topic not found' });
|
||||
}
|
||||
|
||||
const { content } = await readBody(event);
|
||||
if (!content) {
|
||||
throw createError({ statusCode: 400, statusMessage: 'No content provided' });
|
||||
}
|
||||
|
||||
const [message] = await db.insert(messages).values({
|
||||
topicId: topic.id,
|
||||
userId: event.context.user.id,
|
||||
content,
|
||||
isUser: true,
|
||||
}).returning();
|
||||
|
||||
return message;
|
||||
});
|
||||
@@ -1,11 +1,14 @@
|
||||
import { topics } from "~~/db/schema";
|
||||
import { protectRoute } from "~~/server/utils/auth";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
await protectRoute(event);
|
||||
|
||||
const db = useDrizzle();
|
||||
const userId = event.context.user.id;
|
||||
|
||||
const rows = await db.select().from(topics);
|
||||
// Only return topics for the authenticated user
|
||||
const rows = await db.select().from(topics).where(eq(topics.userId, userId));
|
||||
return rows;
|
||||
});
|
||||
@@ -1,16 +1,30 @@
|
||||
import { topics } from "~~/db/schema";
|
||||
import { topics, agents } from "~~/db/schema";
|
||||
import { protectRoute } from "~~/server/utils/auth";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
await protectRoute(event);
|
||||
|
||||
const db = useDrizzle();
|
||||
const userId = event.context.user.id;
|
||||
|
||||
const body = await readBody(event);
|
||||
const { agentId, name } = body;
|
||||
|
||||
if (!agentId || !name) {
|
||||
throw createError({ statusCode: 400, statusMessage: 'Missing required fields' });
|
||||
}
|
||||
const [inserted] = await db.insert(topics).values({ userId: event.context.user.id, agentId, name }).returning();
|
||||
|
||||
// Verify the agent belongs to this user
|
||||
const [agent] = await db.select().from(agents).where(eq(agents.id, agentId));
|
||||
if (!agent || agent.userId !== userId) {
|
||||
throw createError({ statusCode: 403, statusMessage: 'Agent not found or unauthorized' });
|
||||
}
|
||||
|
||||
const [inserted] = await db
|
||||
.insert(topics)
|
||||
.values({ userId, agentId, name })
|
||||
.returning();
|
||||
|
||||
return inserted;
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
export type MessageType = 'system' | 'agent' | 'user';
|
||||
export type GenerationStatus = 'pending' | 'active' | 'completed' | 'failed';
|
||||
|
||||
export interface ChatMessage {
|
||||
type: MessageType;
|
||||
@@ -8,19 +9,21 @@ export interface ChatMessage {
|
||||
export interface GenerateRequestBody {
|
||||
topicId: string;
|
||||
messages: ChatMessage[];
|
||||
regeneratesFrom?: string;
|
||||
}
|
||||
|
||||
export interface GenerationStreamEvent {
|
||||
type: 'token' | 'complete' | 'error';
|
||||
type: 'start' | 'token' | 'complete' | 'error';
|
||||
data: string | object | null;
|
||||
}
|
||||
|
||||
export interface GenerationStatus {
|
||||
export interface GenerationStatusResponse {
|
||||
generationId: string;
|
||||
status: 'pending' | 'active' | 'completed' | 'error';
|
||||
status: GenerationStatus;
|
||||
content?: string;
|
||||
topicId?: string;
|
||||
model?: string;
|
||||
tokensGenerated?: number;
|
||||
tokensUsedThinking?: number;
|
||||
error?: string;
|
||||
}
|
||||
+197
-122
@@ -1,70 +1,63 @@
|
||||
import { useDrizzle } from '~~/server/utils/drizzle';
|
||||
import { generations, messages as messages_drizzle } from '~~/db/schema';
|
||||
import { type GenerationStreamEvent, type ChatMessage, type MessageType } from '~~/server/types/chat';
|
||||
import { generations, messages as messages_drizzle, messagesRelations } from '~~/db/schema';
|
||||
import { type GenerationStreamEvent, type ChatMessage, type GenerationStatus } from '~~/server/types/chat';
|
||||
import { eq } from 'drizzle-orm';
|
||||
|
||||
interface ActiveGeneration {
|
||||
/**
|
||||
* Streaming generation state - only stores active stream controllers
|
||||
* All persistent state lives in the database
|
||||
*/
|
||||
interface ActiveGenerationStream {
|
||||
userId: string;
|
||||
topicId: string;
|
||||
messages: ChatMessage[];
|
||||
content: string;
|
||||
clients: Set<ReadableStreamDefaultController<Uint8Array>>;
|
||||
complete: boolean;
|
||||
isGenerating: boolean;
|
||||
}
|
||||
|
||||
interface PendingGeneration {
|
||||
generationId: string;
|
||||
userId: string;
|
||||
topicId: string;
|
||||
messages: ChatMessage[];
|
||||
timeout: NodeJS.Timeout;
|
||||
expired: boolean;
|
||||
}
|
||||
|
||||
const activeGenerations = new Map<string, ActiveGeneration>();
|
||||
const pendingGenerations = new Map<string, PendingGeneration>();
|
||||
|
||||
export const getActiveGeneration = (generationId: string): ActiveGeneration | undefined => {
|
||||
return activeGenerations.get(generationId);
|
||||
};
|
||||
|
||||
export const getPendingGeneration = (generationId: string): PendingGeneration | undefined => {
|
||||
return pendingGenerations.get(generationId);
|
||||
};
|
||||
|
||||
export const isGenerationActive = (generationId: string): boolean => {
|
||||
return activeGenerations.has(generationId);
|
||||
};
|
||||
|
||||
export const isGenerationPending = (generationId: string): boolean => {
|
||||
return pendingGenerations.has(generationId);
|
||||
};
|
||||
const activeGenerationStreams = new Map<string, ActiveGenerationStream>();
|
||||
const db = useDrizzle();
|
||||
|
||||
/**
|
||||
* Add a client connection to an active generation stream
|
||||
*/
|
||||
export const addClientToGeneration = (generationId: string, controller: ReadableStreamDefaultController<Uint8Array>): boolean => {
|
||||
const generation = activeGenerations.get(generationId);
|
||||
if (!generation) {
|
||||
const stream = activeGenerationStreams.get(generationId);
|
||||
if (!stream) {
|
||||
return false;
|
||||
}
|
||||
generation.clients.add(controller);
|
||||
stream.clients.add(controller);
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a client connection from an active generation stream
|
||||
*/
|
||||
export const removeClientFromGeneration = (generationId: string, controller: ReadableStreamDefaultController<Uint8Array>): void => {
|
||||
const generation = activeGenerations.get(generationId);
|
||||
if (generation) {
|
||||
generation.clients.delete(controller);
|
||||
if (generation.clients.size === 0 && generation.complete) {
|
||||
activeGenerations.delete(generationId);
|
||||
const stream = activeGenerationStreams.get(generationId);
|
||||
if (stream) {
|
||||
stream.clients.delete(controller);
|
||||
// Clean up if no clients left and generation is complete
|
||||
if (stream.clients.size === 0 && !stream.isGenerating) {
|
||||
activeGenerationStreams.delete(generationId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const sendToClients = (generation: ActiveGeneration, event: GenerationStreamEvent): void => {
|
||||
for (const client of generation.clients) {
|
||||
/**
|
||||
* Send an event to all connected clients for a generation
|
||||
*/
|
||||
export const sendToClients = (generationId: string, event: GenerationStreamEvent): void => {
|
||||
const stream = activeGenerationStreams.get(generationId);
|
||||
if (!stream) return;
|
||||
|
||||
for (const client of stream.clients) {
|
||||
sendToClient(client, event);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Send an event to a single client
|
||||
*/
|
||||
export const sendToClient = (client: ReadableStreamDefaultController<Uint8Array>, event: GenerationStreamEvent): void => {
|
||||
const data = JSON.stringify(event);
|
||||
const encoder = new TextEncoder();
|
||||
@@ -73,12 +66,15 @@ export const sendToClient = (client: ReadableStreamDefaultController<Uint8Array>
|
||||
} catch (error) {
|
||||
console.error('Failed to send to client:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Build a prompt from chat messages
|
||||
*/
|
||||
const buildPrompt = (messages: ChatMessage[]): string => {
|
||||
return messages
|
||||
.map((msg: ChatMessage) => {
|
||||
const roleMap: Record<MessageType, string> = {
|
||||
const roleMap: Record<typeof msg.type, string> = {
|
||||
system: 'System',
|
||||
user: 'User',
|
||||
agent: 'Assistant'
|
||||
@@ -88,121 +84,200 @@ const buildPrompt = (messages: ChatMessage[]): string => {
|
||||
.join('\n\n');
|
||||
};
|
||||
|
||||
const db = useDrizzle();
|
||||
|
||||
export const startGeneration = async (generationId: string, controller: ReadableStreamDefaultController<Uint8Array>): Promise<void> => {
|
||||
const pending = pendingGenerations.get(generationId);
|
||||
|
||||
if (!pending) {
|
||||
console.error(`Generation ${generationId} not found in pending generations`);
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(pending.timeout);
|
||||
pendingGenerations.delete(generationId);
|
||||
|
||||
const { userId, topicId, messages } = pending;
|
||||
const prompt = buildPrompt(messages);
|
||||
|
||||
const generation: ActiveGeneration = {
|
||||
/**
|
||||
* Create a new pending generation in the database
|
||||
* Returns the generation ID
|
||||
*/
|
||||
export const createPendingGeneration = async (userId: string, topicId: string, messages: ChatMessage[], regeneratesFrom?: string): Promise<string> => {
|
||||
const generationValues: any = {
|
||||
userId,
|
||||
topicId,
|
||||
messages,
|
||||
content: '',
|
||||
clients: new Set([controller]),
|
||||
complete: false
|
||||
status: 'pending' as GenerationStatus,
|
||||
};
|
||||
|
||||
await db.insert(generations).values({
|
||||
id: generationId,
|
||||
userId,
|
||||
topicId,
|
||||
messageId: null
|
||||
});
|
||||
if (regeneratesFrom) {
|
||||
generationValues.regeneratesFrom = regeneratesFrom;
|
||||
}
|
||||
|
||||
activeGenerations.set(generationId, generation);
|
||||
const [generation] = await db
|
||||
.insert(generations)
|
||||
.values(generationValues)
|
||||
.returning();
|
||||
|
||||
return generation.id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get current generation status and content from database
|
||||
*/
|
||||
export const getGenerationStatus = async (generationId: string) => {
|
||||
const [generation] = await db
|
||||
.select()
|
||||
.from(generations)
|
||||
.where(eq(generations.id, generationId));
|
||||
|
||||
return generation || null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Start a generation: update status to active and begin streaming
|
||||
* This is called when a client connects to the stream
|
||||
*/
|
||||
export const startGeneration = async (
|
||||
generationId: string,
|
||||
userId: string,
|
||||
topicId: string,
|
||||
messages: ChatMessage[],
|
||||
controller: ReadableStreamDefaultController<Uint8Array>
|
||||
): Promise<void> => {
|
||||
try {
|
||||
// Get current generation from database
|
||||
const generation = await getGenerationStatus(generationId);
|
||||
if (!generation) {
|
||||
throw new Error('Generation not found');
|
||||
}
|
||||
|
||||
// Create active stream tracking
|
||||
activeGenerationStreams.set(generationId, {
|
||||
userId,
|
||||
topicId,
|
||||
clients: new Set([controller]),
|
||||
isGenerating: true,
|
||||
});
|
||||
|
||||
// Update status to active
|
||||
await db
|
||||
.update(generations)
|
||||
.set({
|
||||
status: 'active' as GenerationStatus,
|
||||
startedAt: new Date(),
|
||||
})
|
||||
.where(eq(generations.id, generationId));
|
||||
|
||||
sendToClients(generationId, {
|
||||
type: 'start',
|
||||
data: null,
|
||||
});
|
||||
|
||||
const prompt = buildPrompt(messages);
|
||||
const dummyResponse = generateDummyResponse(prompt, messages);
|
||||
const tokens = dummyResponse.split(' ');
|
||||
|
||||
// Simulate token streaming
|
||||
let accumulatedContent = '';
|
||||
for (const token of tokens) {
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
|
||||
generation.content += token + ' ';
|
||||
accumulatedContent += token + ' ';
|
||||
|
||||
sendToClients(generation, {
|
||||
sendToClients(generationId, {
|
||||
type: 'token',
|
||||
data: token + ' '
|
||||
data: token + ' ',
|
||||
});
|
||||
}
|
||||
|
||||
const [message] = await db.insert(messages_drizzle).values({
|
||||
topicId: generation.topicId,
|
||||
userId: generation.userId,
|
||||
const finalContent = accumulatedContent.trim();
|
||||
|
||||
// Create message record for this generation
|
||||
const messageValues: any = {
|
||||
topicId,
|
||||
userId,
|
||||
content: finalContent,
|
||||
isUser: false,
|
||||
content: generation.content.trim(),
|
||||
model: 'dummy-model-v1',
|
||||
tokensGenerated: tokens.length,
|
||||
tokensUsedThinking: 0
|
||||
}).returning();
|
||||
};
|
||||
|
||||
generation.complete = true;
|
||||
// If this is a regeneration, set the regeneratedFromId
|
||||
if (generation.regeneratesFrom) {
|
||||
messageValues.regeneratedFromId = generation.regeneratesFrom;
|
||||
messageValues.isRegenerated = true;
|
||||
}
|
||||
|
||||
sendToClients(generation, {
|
||||
let [message] = await db
|
||||
.insert(messages_drizzle)
|
||||
.values(messageValues)
|
||||
.returning();
|
||||
|
||||
// Update generation as completed
|
||||
await db
|
||||
.update(generations)
|
||||
.set({
|
||||
status: 'completed' as GenerationStatus,
|
||||
completedAt: new Date(),
|
||||
messageId: message.id,
|
||||
})
|
||||
.where(eq(generations.id, generationId));
|
||||
|
||||
let fmessage = await db.select().from(messages_drizzle).where(eq(messages_drizzle.userId, userId)).leftJoin(generations, eq(messages_drizzle.id, generations.messageId))
|
||||
console.log(fmessage);
|
||||
|
||||
// Mark stream as no longer generating
|
||||
const stream = activeGenerationStreams.get(generationId);
|
||||
if (stream) {
|
||||
stream.isGenerating = false;
|
||||
}
|
||||
|
||||
sendToClients(generationId, {
|
||||
type: 'complete',
|
||||
data: message
|
||||
data: fmessage,
|
||||
});
|
||||
|
||||
if (generation.clients.size === 0) {
|
||||
activeGenerations.delete(generationId);
|
||||
} else {
|
||||
generation.clients.forEach((client) => {
|
||||
// Close all client connections
|
||||
const finalStream = activeGenerationStreams.get(generationId);
|
||||
if (finalStream) {
|
||||
for (const client of finalStream.clients) {
|
||||
try {
|
||||
client.close();
|
||||
} catch {
|
||||
} catch (error) {
|
||||
// Client already closed
|
||||
}
|
||||
});
|
||||
activeGenerations.delete(generationId);
|
||||
}
|
||||
activeGenerationStreams.delete(generationId);
|
||||
}
|
||||
|
||||
await db.update(generations).set({
|
||||
messageId: message.id
|
||||
}).where(eq(generations.id, generationId));
|
||||
|
||||
} catch (error) {
|
||||
console.error('Generation failed:', error);
|
||||
|
||||
sendToClients(generation, {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
|
||||
// Update generation as failed
|
||||
await db
|
||||
.update(generations)
|
||||
.set({
|
||||
status: 'failed' as GenerationStatus,
|
||||
error: errorMessage,
|
||||
completedAt: new Date(),
|
||||
})
|
||||
.where(eq(generations.id, generationId));
|
||||
|
||||
sendToClients(generationId, {
|
||||
type: 'error',
|
||||
data: error instanceof Error ? error.message : 'Unknown error'
|
||||
data: { error: errorMessage },
|
||||
});
|
||||
|
||||
await db.delete(generations).where(eq(generations.id, generationId));
|
||||
|
||||
activeGenerations.delete(generationId);
|
||||
// Close all client connections
|
||||
const stream = activeGenerationStreams.get(generationId);
|
||||
if (stream) {
|
||||
for (const client of stream.clients) {
|
||||
try {
|
||||
client.close();
|
||||
} catch (error) {
|
||||
// Client already closed
|
||||
}
|
||||
}
|
||||
activeGenerationStreams.delete(generationId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const registerPendingGeneration = (userId: string, generationId: string, topicId: string, messages: ChatMessage[]): void => {
|
||||
const timeout = setTimeout(() => {
|
||||
const gen = pendingGenerations.get(generationId)
|
||||
if (gen) gen.expired = true;
|
||||
console.log(`Generation ${generationId} expired - no client connected within 60 seconds`);
|
||||
}, 60000);
|
||||
|
||||
pendingGenerations.set(generationId, {
|
||||
generationId,
|
||||
userId,
|
||||
topicId,
|
||||
messages,
|
||||
timeout,
|
||||
expired: false
|
||||
});
|
||||
|
||||
console.log(`Registered pending generation ${generationId}, waiting for client connection...`);
|
||||
/**
|
||||
* Check if a generation is currently being streamed
|
||||
*/
|
||||
export const isGenerationStreaming = (generationId: string): boolean => {
|
||||
return activeGenerationStreams.has(generationId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate a dummy response for testing
|
||||
*/
|
||||
const generateDummyResponse = (prompt: string, messages: ChatMessage[]): string => {
|
||||
const responses = [
|
||||
"This is a simulated response to your prompt. In a real implementation, this would be generated by an AI model like GPT-4 or Claude."
|
||||
@@ -227,4 +302,4 @@ const generateDummyResponse = (prompt: string, messages: ChatMessage[]): string
|
||||
}
|
||||
|
||||
return responses[Math.floor(Math.random() * responses.length)];
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user