Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions app/api/generate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,20 @@ function isGenerateProps(value: unknown): value is GenerateProps {
if (!value || typeof value !== 'object') return false

const body = value as Partial<GenerateProps>
const validChatMode = body.chatMode === undefined
|| body.chatMode === 'general'
|| body.chatMode === 'learn'
|| body.chatMode === 'research'
|| body.chatMode === 'build'

return typeof body.prompt === 'string'
&& typeof body.tool === 'string'
&& typeof body.authorId === 'string'
&& (body.authorEmail === undefined || typeof body.authorEmail === 'string')
&& (body.sessionId === undefined || body.sessionId === null || typeof body.sessionId === 'string')
&& (body.chatId === undefined || typeof body.chatId === 'string')
&& (body.researchMode === undefined || typeof body.researchMode === 'boolean')
&& validChatMode
&& (body.chatMode === undefined || isChatMode(body.chatMode))
&& (body.attachments === undefined || Array.isArray(body.attachments))
}
Expand All @@ -48,6 +55,8 @@ export async function POST(request: Request) {
}, { status: 400 })
}

try {
return NextResponse.json(await generateAnswerForPrompt(body))
const chatMode = normalizeChatMode(body.chatMode)

if (!chatMode) {
Expand Down
21 changes: 21 additions & 0 deletions components/PromptShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import React, { ChangeEvent, useCallback, useEffect, useRef, useState, useTransition } from 'react'
import Image from 'next/image'
import type { GenerateAnswerResult, GenerateProps, TeraChatMode } from '@/lib/generate-types'
import type { GenerateAnswerResult, GenerateProps } from '@/lib/generate-types'
import { CHAT_MODES, getChatModeConfig, isChatMode, type ChatMode } from '@/lib/chat-modes'
import type { TeacherTool } from './ToolCard'
Expand Down Expand Up @@ -52,6 +53,24 @@ type QueuedMessage = {

const createId = () => (crypto.randomUUID ? crypto.randomUUID() : String(Date.now()))

const inferChatMode = (tool: TeacherTool, researchMode: boolean): TeraChatMode => {
if (researchMode) return 'research'

const searchable = `${tool.name} ${tool.description} ${tool.tags.join(' ')}`.toLowerCase()

if (/(research|deep dive|analysis|citation|data|reading|resource|investigation|web)/.test(searchable)) {
return 'research'
}

if (/(build|builder|plan|planner|generator|creator|project|resume|rubric|lesson|worksheet|materials|spreadsheet)/.test(searchable)) {
return 'build'
}

if (/(learn|study|homework|concept|explain|clarifier|quiz|practice|language|math|skill)/.test(searchable)) {
return 'learn'
}

return 'general'
const getChatModeForTool = (toolName: string): ChatMode => {
const normalized = toolName.toLowerCase()

Expand Down Expand Up @@ -415,6 +434,7 @@ export default function PromptShell({
sessionId: currentSessionId,
chatId: editingMessageId ?? undefined,
researchMode,
chatMode: inferChatMode(tool, researchMode)
chatMode: outgoingChatMode,
})

Expand Down Expand Up @@ -443,6 +463,7 @@ export default function PromptShell({
setEditingMessageId(null)
setQueuedMessage(null)
})
}, [editingMessageId, hasBumpedInput, tool, user?.id, user?.email, currentSessionId, researchMode])
}, [editingMessageId, hasBumpedInput, tool.name, user?.id, user?.email, currentSessionId, researchMode])

const handleSaveNote = async (assistantMessage: Message) => {
Expand Down
5 changes: 5 additions & 0 deletions lib/generate-answer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ function omitField<T extends Record<string, any>, K extends keyof T>(payload: T,
return rest
}

export async function generateAnswerForPrompt({ prompt, tool, authorId, authorEmail, attachments = [], sessionId, chatId, researchMode = false, chatMode = researchMode ? 'research' : 'general' }: GenerateProps): Promise<GenerateAnswerResult> {
// Get user profile and check limits
export async function generateAnswerForPrompt({
prompt,
tool,
Expand Down Expand Up @@ -124,6 +126,7 @@ export async function generateAnswerForPrompt({
chatId: chatId ?? null,
resetDate,
promptLength: prompt.length,
chatMode,
chatMode: normalizedChatMode,
},
})
Expand Down Expand Up @@ -176,6 +179,8 @@ export async function generateAnswerForPrompt({
}
}

// Generate the AI response
const generationResult = await generateTeacherResponse({ prompt, tool, attachments, history, userId: authorId, researchMode, chatMode })
const generationResult = await generateTeacherResponse({
prompt,
tool,
Expand Down
3 changes: 3 additions & 0 deletions lib/generate-types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { AttachmentReference } from '@/lib/attachment'

export type TeraChatMode = 'general' | 'learn' | 'research' | 'build'
import type { ChatMode } from '@/lib/ai/chat-modes'

export type GenerateProps = {
Expand All @@ -10,6 +12,7 @@ export type GenerateProps = {
sessionId?: string | null
chatId?: string
researchMode?: boolean
chatMode?: TeraChatMode
chatMode?: ChatMode
}

Expand Down
28 changes: 28 additions & 0 deletions lib/mistral.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { AttachmentReference } from './attachment'
import type { TeraChatMode } from './generate-types'
import type { ChatMode } from './ai/chat-modes'
import { getChatModeSystemPrompt, normalizeChatMode } from './ai/chat-modes'
import { extractTextFromFile } from './extract-text'
Expand Down Expand Up @@ -71,6 +72,30 @@ GOOGLE SHEETS AND SPREADSHEETS:
- For spreadsheet edits, generate edit instructions in a json:edit block.
`

function getToolResponseStyle(tool: string, researchMode: boolean, chatMode: TeraChatMode): string {
if (chatMode === 'research') {
return `\nMode Guidance:
- Act like a precise research partner.
- Surface the answer first, then the reasoning, evidence, tradeoffs, and implications.
- Distinguish clearly between what is known, what is likely, and what remains uncertain.
- Prefer synthesis over volume.
- Use citations and links where they add real value.`
}

if (chatMode === 'build') {
return `\nMode Guidance:
- Act like a practical builder's assistant.
- Turn ideas into steps, decisions, examples, checklists, or implementation plans.
- Be concrete and execution-oriented.
- Call out tradeoffs, constraints, and the next action.`
}

if (chatMode === 'learn') {
return `\nMode Guidance:
- Act like a strong teacher.
- Explain from first principles.
- Use one simple mental model or worked example when it helps.
- Keep the explanation approachable without sounding childish.`
function getToolResponseStyle(tool: string, researchMode: boolean, chatMode: ChatMode): string {
if (chatMode === 'study') {
return `\nMode Guidance:
Expand Down Expand Up @@ -220,6 +245,7 @@ export async function generateTeacherResponse({
history = [] as { role: 'user' | 'assistant'; content: string }[],
userId,
researchMode = false,
chatMode = researchMode ? 'research' : 'general',
chatMode = 'ask',
}: {
prompt: string
Expand All @@ -228,6 +254,7 @@ export async function generateTeacherResponse({
history?: { role: 'user' | 'assistant'; content: string }[]
userId?: string
researchMode?: boolean
chatMode?: TeraChatMode
chatMode?: ChatMode
}) {
const imageAttachments = attachments.filter((att) => att.type === 'image')
Expand Down Expand Up @@ -298,6 +325,7 @@ ${modeSystemPrompt}
- Keep the explanation clean and easy to scan.
- Use one example or practical takeaway when it helps.
- End naturally. Do not force generic follow-up questions.`
const toolStyle = getToolResponseStyle(tool, researchMode, chatMode)
const toolStyle = getToolResponseStyle(tool, researchMode, normalizedChatMode)

toolContext += `\nChat Mode: ${chatMode}.`
Expand Down