chore: ai, public course and other ui improvements

This commit is contained in:
Rotimi Best
2026-05-09 06:05:03 +01:00
parent 5717dd8004
commit c31ad63955
318 changed files with 20668 additions and 4788 deletions
+61 -10
View File
@@ -1,4 +1,5 @@
import { generateText } from 'ai';
import { generateText, generateObject } from 'ai';
import { z } from 'zod';
import { AIProvider, type AIProviderConfig } from '@cio/ai-assistant';
import { createOpenAI } from '@ai-sdk/openai';
import { createAnthropic } from '@ai-sdk/anthropic';
@@ -9,8 +10,9 @@ import { createGoogleGenerativeAI } from '@ai-sdk/google';
*/
const TITLE_MODELS: Record<AIProvider, string> = {
[AIProvider.OPENAI]: 'gpt-4o-mini',
[AIProvider.ANTHROPIC]: 'claude-haiku-4-20250414',
[AIProvider.GOOGLE]: 'gemini-2.5-flash'
[AIProvider.ANTHROPIC]: 'claude-haiku-4-5-20251001',
[AIProvider.GOOGLE]: 'gemini-2.5-flash',
[AIProvider.MOONSHOT]: 'kimi-k2.6'
};
function createTitleModel(config: AIProviderConfig) {
@@ -29,6 +31,10 @@ function createTitleModel(config: AIProviderConfig) {
const google = createGoogleGenerativeAI({ apiKey: config.apiKey });
return google(modelName);
}
case AIProvider.MOONSHOT: {
const moonshot = createOpenAI({ apiKey: config.apiKey, baseURL: 'https://api.moonshot.cn/v1' });
return moonshot(modelName);
}
default:
throw new Error(`Unsupported AI provider: ${config.provider}`);
}
@@ -44,13 +50,58 @@ export async function generateConversationTitle(
): Promise<string> {
const model = createTitleModel(providerConfig);
const { text } = await generateText({
model,
system:
"Generate a short title (max 6 words) for a chat conversation based on the user's first message. Return ONLY the title, no quotes, no punctuation at the end. Be concise and descriptive.",
prompt: firstMessageText,
maxOutputTokens: 30
});
const systemPrompt =
"Generate a short, descriptive title (2–5 words) for a chat conversation based on the user's first message. The title must be at least 2 words — never a single word. Return ONLY the title, no quotes, no punctuation at the end.";
const truncatedMessage = firstMessageText.slice(0, 1000);
// Gemini 2.5 counts thinking toward maxOutputTokens; disable thinking and allow headroom for the title text.
const { text } = await generateText(
providerConfig.provider === AIProvider.GOOGLE
? {
model,
system: systemPrompt,
prompt: truncatedMessage,
maxOutputTokens: 256,
maxRetries: 0,
providerOptions: {
google: { thinkingConfig: { thinkingBudget: 0 } }
}
}
: {
model,
system: systemPrompt,
prompt: truncatedMessage,
maxOutputTokens: 96,
maxRetries: 0
}
);
return text.trim().slice(0, 80);
}
const CourseMeta = z.object({
title: z.string().describe('3–6 word course title, no quotes, no trailing punctuation'),
description: z.string().describe('20–30 word description of what students will learn')
});
export async function generateCourseMeta(
prompt: string,
providerConfig: AIProviderConfig
): Promise<{ title: string; description: string }> {
const model = createTitleModel(providerConfig);
const { object } = await generateObject({
model,
schema: CourseMeta,
system:
"You are helping create an online course. Based on the user's course idea, generate a short title (3–6 words, no quotes, no trailing punctuation) and a concise description (20–30 words) explaining what students will learn.",
prompt: prompt.slice(0, 1000),
maxRetries: 0
});
return {
title: object.title.trim().slice(0, 100),
description: object.description.trim().slice(0, 300)
};
}