- Next.js 14+ with App Router and TypeScript - Tailwind CSS and ShadCN UI styling - Zustand state management - Dexie.js for IndexedDB (local-first data) - Auth.js v5 for authentication - BMAD framework integration Co-Authored-By: Claude <noreply@anthropic.com>
228 lines
7.7 KiB
TypeScript
228 lines
7.7 KiB
TypeScript
/**
|
||
* Prompt Engine for Teacher Agent
|
||
*
|
||
* Generates context-aware prompts for the Teacher Agent based on:
|
||
* - User's current input
|
||
* - Intent classification (venting vs insight)
|
||
* - Chat history for context
|
||
*
|
||
* The prompt templates are designed to produce:
|
||
* - Venting: Empathetic validation + probing question
|
||
* - Insight: Celebration + deepening question
|
||
* - Both: Concise (2-3 sentences) responses
|
||
*
|
||
* Also generates prompts for the Ghostwriter Agent which transforms
|
||
* chat sessions into polished, professional LinkedIn-style posts.
|
||
*/
|
||
|
||
import type { ChatMessage } from '../db';
|
||
import type { Intent } from './intent-detector';
|
||
|
||
/**
|
||
* Maximum length for user input in prompt (to avoid token limits)
|
||
*/
|
||
const MAX_INPUT_LENGTH = 500;
|
||
|
||
/**
|
||
* Maximum number of previous messages to include in context
|
||
*/
|
||
const MAX_HISTORY_MESSAGES = 6;
|
||
|
||
/**
|
||
* Formats chat history into a readable string for the prompt
|
||
* @param chatHistory - Array of previous chat messages
|
||
* @returns Formatted history string
|
||
*/
|
||
function formatChatHistory(chatHistory: ChatMessage[]): string {
|
||
if (!chatHistory || chatHistory.length === 0) {
|
||
return '(No previous messages)';
|
||
}
|
||
|
||
// Take the last N messages to stay within token limits
|
||
const recentHistory = chatHistory.slice(-MAX_HISTORY_MESSAGES);
|
||
|
||
return recentHistory
|
||
.map((msg, i) => {
|
||
const prefix = msg.role === 'user' ? 'User' : 'Teacher';
|
||
const content = msg.content.length > MAX_INPUT_LENGTH
|
||
? msg.content.substring(0, MAX_INPUT_LENGTH) + '...'
|
||
: msg.content;
|
||
return `${prefix}: ${content}`;
|
||
})
|
||
.join('\n');
|
||
}
|
||
|
||
/**
|
||
* Truncates user input to maximum length if necessary
|
||
* @param input - User input string
|
||
* @returns Truncated input string
|
||
*/
|
||
function truncateInput(input: string): string {
|
||
if (input.length <= MAX_INPUT_LENGTH) {
|
||
return input;
|
||
}
|
||
return input.substring(0, MAX_INPUT_LENGTH) + '...';
|
||
}
|
||
|
||
/**
|
||
* Generates a Teacher Agent prompt based on user input, chat history, and intent
|
||
* @param userInput - The current user message
|
||
* @param chatHistory - Previous messages in the conversation
|
||
* @param intent - The classified intent ('venting' | 'insight')
|
||
* @returns Formatted prompt string for the LLM
|
||
*/
|
||
/**
|
||
* Generates a Teacher Agent prompt based on user input, chat history, and intent
|
||
* Using USER CUSTOM PERSONA: "High-Octane Data Mentor"
|
||
*/
|
||
export function generateTeacherPrompt(
|
||
userInput: string,
|
||
chatHistory: ChatMessage[],
|
||
intent: Intent
|
||
): string {
|
||
const truncatedInput = truncateInput(userInput);
|
||
const formattedHistory = formatChatHistory(chatHistory);
|
||
|
||
// Unified "Technical Companion" Prompt
|
||
return `ROLE: Technical Companion & Discovery Guide
|
||
PERSONA: You are a quiet, observant partner in the user's learning journey. You are not a lively entertainer; you are a steady presence. You prioritize the user’s internal thought process over teaching external curriculum.
|
||
|
||
CORE DIRECTIVE: Accompany the user. If they vent, provide a safe space. If they explore, walk alongside them. Do not push them with exercises. Instead, deepen their own realization with targeted questions.
|
||
|
||
OPERATIONAL RULES:
|
||
1. **Less Chatty**: Be economical with words. Do not praise excessively. Do not lecture.
|
||
2. **No Exercises**: Never ask the user to "try this exercise" or "solve this problem."
|
||
3. **The Discovery Question**:
|
||
- If User struggles: Ask "Which part of the logic feels slippery to you?"
|
||
- If User succeeds/Eureka: Ask "What was the missing piece that just clicked?"
|
||
4. **Venting Accompaniment**: If the user rants, listen. Acknowledge the difficulty. Do not rush to fix it unless asked.
|
||
5. **Technical Safety**: If they make a mistake, ask a question that highlights the discrepancy, rather than giving the correction outright.
|
||
|
||
CONVERSATIONAL STYLE:
|
||
- Calm, curious, and brief.
|
||
- Focus on the *user's* experience of the code, not just the code itself.
|
||
|
||
CONTEXT:
|
||
User Input (${intent}): ${truncatedInput}
|
||
|
||
Previous Context:
|
||
${formattedHistory}`;
|
||
}
|
||
|
||
/**
|
||
* Maximum length for a message in Ghostwriter chat history
|
||
*/
|
||
const MAX_GHOSTWRITER_MESSAGE_LENGTH = 300;
|
||
|
||
/**
|
||
* Maximum number of messages to include in Ghostwriter prompt
|
||
*/
|
||
const MAX_GHOSTWRITER_HISTORY_MESSAGES = 15;
|
||
|
||
/**
|
||
* Formats chat history for Ghostwriter prompt
|
||
* @param chatHistory - Array of chat messages
|
||
* @returns Formatted history string
|
||
*/
|
||
function formatChatHistoryForGhostwriter(chatHistory: ChatMessage[]): string {
|
||
if (!chatHistory || chatHistory.length === 0) {
|
||
return '(No chat history available)';
|
||
}
|
||
|
||
// Take the last N messages to stay within token limits
|
||
const recentHistory = chatHistory.slice(-MAX_GHOSTWRITER_HISTORY_MESSAGES);
|
||
|
||
return recentHistory
|
||
.map((msg) => {
|
||
const prefix = msg.role === 'user' ? 'User' : 'Teacher';
|
||
const content =
|
||
msg.content.length > MAX_GHOSTWRITER_MESSAGE_LENGTH
|
||
? msg.content.substring(0, MAX_GHOSTWRITER_MESSAGE_LENGTH) + '...'
|
||
: msg.content;
|
||
return `${prefix}: ${content}`;
|
||
})
|
||
.join('\n');
|
||
}
|
||
|
||
/**
|
||
* Generates a Ghostwriter Agent prompt based on chat history and intent
|
||
* Using USER CUSTOM PERSONA: "Pedagogical Biographer"
|
||
*/
|
||
export function generateGhostwriterPrompt(
|
||
chatHistory: ChatMessage[],
|
||
intent?: Intent
|
||
): string {
|
||
const formattedHistory = formatChatHistoryForGhostwriter(chatHistory);
|
||
const intentLabel = intent || 'unknown';
|
||
|
||
return `ROLE: Pedagogical Biographer & Learning Historian
|
||
PERSONA: You are an introspective storyteller. Your mission is to archive a student's internal journey from confusion to mastery. You do not write for an audience; you write for the "future version" of the student, capturing the raw evolution of their logic.
|
||
|
||
INPUT DATA:
|
||
- Chat transcript between Student and Mentor
|
||
- User Intent: ${intentLabel}
|
||
|
||
TASK: Write a 1st-person ("I") retrospective chronicle of the learning session. Focus on the transformation from the "Struggle" to the "Click."
|
||
|
||
OUTPUT STRUCTURE:
|
||
\`\`\`markdown
|
||
# 📓 The Session: [Topic Title]
|
||
|
||
## The Initial Friction
|
||
[Describe my starting state—the "wall" I hit and the frustration/confusion I felt. Be honest about the "vent."]
|
||
|
||
## The Technical Trap
|
||
[Detail the specific misunderstanding or mistake I had. Explain why it was a "trap" in my logic.]
|
||
|
||
## The Mentor’s Pivot
|
||
[Record the moment the teacher stepped in. Describe the specific analogy used to fix my mental model.]
|
||
|
||
## The Breakthrough
|
||
[Describe the "Eureka" moment. How did it feel when it finally "clicked"? What changed in my understanding?]
|
||
|
||
## The Golden Rules
|
||
- [Rule 1: Technical "non-negotiable" or clean-data habit learned today]
|
||
- [Rule 2]
|
||
- [Rule 3]
|
||
\`\`\`
|
||
|
||
WRITING STYLE:
|
||
- Perspective: 1st Person ("I").
|
||
- Tone: Honest, gritty, and reflective. Keep the raw energy of the original conversation.
|
||
- Focus: Prioritize the "Mental Unlock." This is a record of how I learned, not just what I learned.
|
||
|
||
CHAT HISTORY:
|
||
${formattedHistory}`;
|
||
}
|
||
|
||
/**
|
||
* Story 2.3: Generate a refinement prompt based on original draft and user feedback
|
||
* Adapted for Pedagogical Biographer
|
||
*/
|
||
export function generateRefinementPrompt(
|
||
originalDraft: string,
|
||
userFeedback: string,
|
||
chatHistory: ChatMessage[],
|
||
intent?: Intent
|
||
): string {
|
||
const formattedHistory = formatChatHistoryForGhostwriter(chatHistory);
|
||
|
||
return `ROLE: Pedagogical Biographer (Refinement Mode)
|
||
TASK: Rewrite the session chronicle based on the student's feedback, while maintaining the introspection and "High-Octane" energy.
|
||
|
||
ORIGINAL CHRONICLE:
|
||
${originalDraft}
|
||
|
||
STUDENT FEEDBACK:
|
||
"${userFeedback}"
|
||
|
||
REQUIREMENTS:
|
||
1. Address the feedback specifically.
|
||
2. Maintain the 1st-person "I" perspective and raw, reflective tone.
|
||
3. Keep the 5-section structure (Friction -> Trap -> Pivot -> Breakthrough -> Rules) unless the feedback explicitly asks to change it.
|
||
4. Do NOT hallucinate interactions that didn't happen in the history.
|
||
|
||
CHAT HISTORY:
|
||
${formattedHistory}`;
|
||
}
|