feat: widgets and ai chat

This commit is contained in:
Rotimi Best
2026-04-27 11:40:38 +01:00
parent ae1801f85f
commit 5717dd8004
391 changed files with 28177 additions and 22467 deletions
@@ -0,0 +1,171 @@
import { BaseApiWithErrors, classroomio, apiClient, getRequestBaseUrl } from '$lib/utils/services/api';
import type {
AgentConversation,
AgentConversationCreateData,
AgentConversationSummary,
AgentStatusData,
AiAssistantMessage
} from '../utils/types';
class AiAssistantApi extends BaseApiWithErrors {
status: AgentStatusData | null = $state(null);
conversations: AgentConversationSummary[] = $state([]);
currentConversation: AgentConversation | null = $state(null);
async fetchStatus(courseId: string) {
await this.execute<typeof classroomio.agent.status.$get>({
requestFn: () =>
classroomio.agent.status.$get({
query: { courseId }
}),
logContext: 'fetching agent status',
onSuccess: (result) => {
this.status = result.data;
}
});
}
async listConversations(courseId: string) {
await this.execute<typeof classroomio.agent.history.$get>({
requestFn: () =>
classroomio.agent.history.$get({
query: { courseId }
}),
logContext: 'listing conversations',
onSuccess: (result) => {
this.conversations = result.data as AgentConversationSummary[];
}
});
}
async loadConversation(conversationId: string) {
await this.execute<(typeof classroomio.agent.history)[':conversationId']['$get']>({
requestFn: () =>
classroomio.agent.history[':conversationId'].$get({
param: { conversationId }
}),
logContext: 'loading conversation',
onSuccess: (result) => {
this.currentConversation = {
...(result.data as AgentConversation),
messages: ((result.data as AgentConversation).messages ?? []) as AiAssistantMessage[]
};
}
});
}
async createConversation(courseId: string, title?: string): Promise<{ id: string } | null> {
let created: { id: string } | null = null;
await this.execute<typeof classroomio.agent.history.$post>({
requestFn: () =>
classroomio.agent.history.$post({
json: { courseId, title }
}),
logContext: 'creating conversation',
onSuccess: (result) => {
const newConversation = result.data as AgentConversationCreateData;
created = newConversation;
this.conversations = [
{ ...newConversation, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() },
...this.conversations
];
}
});
return created;
}
async saveMessages(conversationId: string, messages: AiAssistantMessage[], title?: string) {
await this.execute<(typeof classroomio.agent.history)[':conversationId']['$put']>({
requestFn: () =>
classroomio.agent.history[':conversationId'].$put({
param: { conversationId },
json: { messages, title }
}),
logContext: 'saving messages'
});
}
async deleteConversation(conversationId: string) {
await this.execute<(typeof classroomio.agent.history)[':conversationId']['$delete']>({
requestFn: () =>
classroomio.agent.history[':conversationId'].$delete({
param: { conversationId }
}),
logContext: 'deleting conversation',
onSuccess: () => {
this.conversations = this.conversations.filter((c) => c.id !== conversationId);
if (this.currentConversation?.id === conversationId) {
this.currentConversation = null;
}
}
});
}
async uploadDocument(
file: File,
courseId: string,
conversationId: string
): Promise<{ documentId: string; fileName: string; wordCount: number; truncated: boolean } | null> {
const formData = new FormData();
formData.append('file', file);
try {
const url =
`${getRequestBaseUrl()}/agent/upload` +
`?courseId=${encodeURIComponent(courseId)}` +
`&conversationId=${encodeURIComponent(conversationId)}`;
const response = await apiClient.request(url, {
method: 'POST',
body: formData,
credentials: 'include'
});
const result = (await response.json()) as {
success: boolean;
data?: { documentId: string; fileName: string; wordCount: number; truncated: boolean };
};
if (result.success && result.data) {
return result.data;
}
} catch (error) {
console.error('Error uploading document:', error);
this.error = 'Failed to upload document';
}
return null;
}
async generateTitle(conversationId: string, firstMessageText: string): Promise<string | null> {
let generatedTitle: string | null = null;
await this.execute<(typeof classroomio.agent.history)[':conversationId']['generate-title']['$post']>({
requestFn: () =>
classroomio.agent.history[':conversationId']['generate-title'].$post({
param: { conversationId },
json: { firstMessageText }
}),
logContext: 'generating title',
onSuccess: (result) => {
generatedTitle = (result.data as { title: string }).title;
// Update the title in the local conversations list
this.conversations = this.conversations.map((c) =>
c.id === conversationId ? { ...c, title: generatedTitle } : c
);
if (this.currentConversation?.id === conversationId) {
this.currentConversation = { ...this.currentConversation, title: generatedTitle };
}
}
});
return generatedTitle;
}
}
export const aiAssistantApi = new AiAssistantApi();