mirror of
https://github.com/Crosstalk-Solutions/project-nomad.git
synced 2026-03-28 03:29:25 +01:00
Content Library: - Children & Family: African Storybooks, Gutenberg children's lit, Wikipedia for Schools, PhET simulations, TED-Ed series - Languages & Reference: Wiktionary in 6 languages (EN/ES/FR/AR/DE/PT) across 3 tiers by language coverage - History & Culture: Project Gutenberg history/biography, History Q&A, American history texts, Wikipedia History & Culture - Legal & Civic: Civics guides, Gutenberg law texts, Law Q&A, Personal Finance Q&A, Wikipedia Politics & Government Chat Export: - New GET /api/chat/sessions/:id/export endpoint - Returns full conversation as a downloadable Markdown file - Filename derived from session title (slugified) - Includes model name, export timestamp, and all messages Roadmap: - Update CATEGORIES-TODO.md to mark 10 categories complete - Add Trades & Vocational and Communications as next high-priority items https://claude.ai/code/session_01WfRC4tDeYprykhMrg4PxX6
148 lines
4.7 KiB
TypeScript
148 lines
4.7 KiB
TypeScript
import { inject } from '@adonisjs/core'
|
|
import type { HttpContext } from '@adonisjs/core/http'
|
|
import { ChatService } from '#services/chat_service'
|
|
import { createSessionSchema, updateSessionSchema, addMessageSchema } from '#validators/chat'
|
|
import KVStore from '#models/kv_store'
|
|
import { SystemService } from '#services/system_service'
|
|
import { SERVICE_NAMES } from '../../constants/service_names.js'
|
|
|
|
@inject()
|
|
export default class ChatsController {
|
|
constructor(private chatService: ChatService, private systemService: SystemService) {}
|
|
|
|
async inertia({ inertia, response }: HttpContext) {
|
|
const aiAssistantInstalled = await this.systemService.checkServiceInstalled(SERVICE_NAMES.OLLAMA)
|
|
if (!aiAssistantInstalled) {
|
|
return response.status(404).json({ error: 'AI Assistant service not installed' })
|
|
}
|
|
|
|
const chatSuggestionsEnabled = await KVStore.getValue('chat.suggestionsEnabled')
|
|
return inertia.render('chat', {
|
|
settings: {
|
|
chatSuggestionsEnabled: chatSuggestionsEnabled ?? false,
|
|
},
|
|
})
|
|
}
|
|
|
|
async index({}: HttpContext) {
|
|
return await this.chatService.getAllSessions()
|
|
}
|
|
|
|
async show({ params, response }: HttpContext) {
|
|
const sessionId = parseInt(params.id)
|
|
const session = await this.chatService.getSession(sessionId)
|
|
|
|
if (!session) {
|
|
return response.status(404).json({ error: 'Session not found' })
|
|
}
|
|
|
|
return session
|
|
}
|
|
|
|
async store({ request, response }: HttpContext) {
|
|
try {
|
|
const data = await request.validateUsing(createSessionSchema)
|
|
const session = await this.chatService.createSession(data.title, data.model)
|
|
return response.status(201).json(session)
|
|
} catch (error) {
|
|
return response.status(500).json({
|
|
error: error instanceof Error ? error.message : 'Failed to create session',
|
|
})
|
|
}
|
|
}
|
|
|
|
async suggestions({ response }: HttpContext) {
|
|
try {
|
|
const suggestions = await this.chatService.getChatSuggestions()
|
|
return response.status(200).json({ suggestions })
|
|
} catch (error) {
|
|
return response.status(500).json({
|
|
error: error instanceof Error ? error.message : 'Failed to get suggestions',
|
|
})
|
|
}
|
|
}
|
|
|
|
async update({ params, request, response }: HttpContext) {
|
|
try {
|
|
const sessionId = parseInt(params.id)
|
|
const data = await request.validateUsing(updateSessionSchema)
|
|
const session = await this.chatService.updateSession(sessionId, data)
|
|
return session
|
|
} catch (error) {
|
|
return response.status(500).json({
|
|
error: error instanceof Error ? error.message : 'Failed to update session',
|
|
})
|
|
}
|
|
}
|
|
|
|
async destroy({ params, response }: HttpContext) {
|
|
try {
|
|
const sessionId = parseInt(params.id)
|
|
await this.chatService.deleteSession(sessionId)
|
|
return response.status(204)
|
|
} catch (error) {
|
|
return response.status(500).json({
|
|
error: error instanceof Error ? error.message : 'Failed to delete session',
|
|
})
|
|
}
|
|
}
|
|
|
|
async addMessage({ params, request, response }: HttpContext) {
|
|
try {
|
|
const sessionId = parseInt(params.id)
|
|
const data = await request.validateUsing(addMessageSchema)
|
|
const message = await this.chatService.addMessage(sessionId, data.role, data.content)
|
|
return response.status(201).json(message)
|
|
} catch (error) {
|
|
return response.status(500).json({
|
|
error: error instanceof Error ? error.message : 'Failed to add message',
|
|
})
|
|
}
|
|
}
|
|
|
|
async exportMarkdown({ params, response }: HttpContext) {
|
|
const sessionId = parseInt(params.id)
|
|
const session = await this.chatService.getSession(sessionId)
|
|
|
|
if (!session) {
|
|
return response.status(404).json({ error: 'Session not found' })
|
|
}
|
|
|
|
const lines: string[] = [
|
|
`# ${session.title}`,
|
|
``,
|
|
`**Model:** ${session.model} `,
|
|
`**Exported:** ${new Date().toUTCString()}`,
|
|
``,
|
|
`---`,
|
|
``,
|
|
]
|
|
|
|
for (const msg of session.messages ?? []) {
|
|
const label = msg.role === 'user' ? '**You**' : '**Assistant**'
|
|
lines.push(`${label}`, ``, msg.content, ``, `---`, ``)
|
|
}
|
|
|
|
const filename = session.title
|
|
.toLowerCase()
|
|
.replace(/[^a-z0-9]+/g, '-')
|
|
.replace(/^-|-$/g, '')
|
|
const markdown = lines.join('\n')
|
|
|
|
response.header('Content-Type', 'text/markdown; charset=utf-8')
|
|
response.header('Content-Disposition', `attachment; filename="${filename}.md"`)
|
|
return response.send(markdown)
|
|
}
|
|
|
|
async destroyAll({ response }: HttpContext) {
|
|
try {
|
|
const result = await this.chatService.deleteAllSessions()
|
|
return response.status(200).json(result)
|
|
} catch (error) {
|
|
return response.status(500).json({
|
|
error: error instanceof Error ? error.message : 'Failed to delete all sessions',
|
|
})
|
|
}
|
|
}
|
|
}
|