continue scaffolding and refine the basic foundation

This commit is contained in:
Zoe
2026-01-13 19:56:24 +00:00
parent 0877cc10bd
commit 8c28946703
37 changed files with 1431 additions and 509 deletions
+16 -9
View File
@@ -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'
});
}
});
+16 -25
View File
@@ -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;
});
+78 -24
View File
@@ -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'
});
}
});