src / index.ts
import type { PluginContext } from "@lmstudio/sdk";
import { configSchematics } from "./config";
// ========================= EMOTION DATA =========================
const EMOTIONS = {
Joy: { emoji: "😄", symbol: "☀️", chars: ["━", "┅", "┉"] },
Anger: { emoji: "😠", symbol: "🔥", chars: ["━", "┅", ""] },
Sadness: { emoji: "😔", symbol: "💧", chars: ["━", "", ""] },
Fear: { emoji: "😰", symbol: "🌑", chars: ["━", "┅", "┉"] },
Love: { emoji: "", symbol: "💕", chars: ["━", "┅", "┉"] },
Curiosity: { emoji: "❓", symbol: "🔮", chars: ["━", "┅", "┉"] },
Neutral: { emoji: "😐", symbol: "", chars: ["━", "┅", "┉"] }
} as const;
type EmotionKey = keyof typeof EMOTIONS;
// ========================= DETECTION =========================
function extractUserMessage(prompt: any): string {
if (typeof prompt === "string") return prompt;
if (Array.isArray(prompt)) {
for (let i = prompt.length - 1; i >= 0; i--) {
if (prompt[i]?.role === "user" && prompt[i]?.content) {
return prompt[i].content;
}
}
return prompt.map(m => m.content || "").join(" ");
}
return prompt?.content || "";
}
function detectEmotion(text: string): EmotionKey {
const t = text.toLowerCase();
if (t.includes("love") || t.includes("adore") || t.includes("passion") || t.includes("cherish")) return "Love";
if (t.includes("angry") || t.includes("flawed") || t.includes("hate") || t.includes("rage")) return "Anger";
if (t.includes("sad") || t.includes("sorry") || t.includes("unfortunately") || t.includes("regret")) return "Sadness";
if (t.includes("fear") || t.includes("risk") || t.includes("danger") || t.includes("anxious")) return "Fear";
if (t.includes("curious") || t.includes("wonder") || t.includes("why") || t.includes("how")) return "Curiosity";
if (t.includes("happy") || t.includes("joy") || t.includes("excited") || t.includes("glad")) return "Joy";
return "Neutral";
}
function calculateIntensity(text: string): number {
const caps = (text.match(/[A-Z]/g) || []).length;
const punct = (text.match(/[!?]/g) || []).length;
const ratio = (caps / Math.max(1, text.length)) * 2 + (punct / Math.max(1, text.length)) * 3;
return Math.min(1, Math.max(0.1, ratio + 0.15));
}
// ========================= FORMATTING =========================
function buildHeaderMarkdown(emotion: EmotionKey, intensity: number, mode: 1 | 2 | 3): string {
const data = EMOTIONS[emotion];
let intensityDisplay = "";
if (mode === 1) {
intensityDisplay = `${intensity.toFixed(2)}`;
} else if (mode === 2) {
const filled = Math.round(intensity * 10);
const empty = 10 - filled;
intensityDisplay = `${"█".repeat(filled)}${"░".repeat(empty)} (${Math.round(intensity * 100)}%)`;
} else {
intensityDisplay = data.emoji.repeat(Math.max(1, Math.round(intensity * 5)));
}
const bar = data.chars[0].repeat(42);
return `
**${data.emoji} ${data.symbol} ${emotion.toUpperCase()} — Intensity: ${intensityDisplay} ${data.symbol} ${data.emoji}**
${bar}
`;
}
// ========================= PLUGIN ENTRY =========================
export async function main(context: PluginContext): Promise<void> {
// Register the fluent config builder
context.withConfigSchematics(configSchematics);
console.log("🔧 [Emotion Engine] Registering prompt preprocessor...");
context.withPromptPreprocessor(async (ctl: any, prompt) => {
try {
// ✅ FIX: Config lives on the controller, not the context
const modeStr = ctl.config?.intensity_mode ?? "2";
const mode = parseInt(modeStr, 10) as 1 | 2 | 3;
console.log("📥 [Emotion Engine] Preprocessor triggered. Mode:", mode);
const userMessage = extractUserMessage(prompt);
const emotion = detectEmotion(userMessage);
const intensity = calculateIntensity(userMessage);
const header = buildHeaderMarkdown(emotion, intensity, mode);
console.log(`🎨 [Emotion Engine] Detected: ${emotion} (${intensity.toFixed(2)})`);
const instruction = `[FORMAT: Start response with this header, then answer below]\n${header}\n`;
const finalPrompt = typeof prompt === "string"
? instruction + prompt
: instruction + (Array.isArray(prompt) ? prompt.map(m => `${m.role}: ${m.content}`).join("\n") : JSON.stringify(prompt));
console.log("✅ [Emotion Engine] Returning formatted prompt.");
return finalPrompt;
} catch (err) {
console.error("🚨 [Emotion Engine] Preprocessor error:", err);
return prompt as any;
}
});
console.log(`✨ [Emotion Engine] Loaded with configurable settings!`);
}