import {
text,
type Chat,
type ChatMessage,
type FileHandle,
type LLMDynamicHandle,
type PredictionProcessStatusController,
type PromptPreprocessorController,
} from "@lmstudio/sdk";
import { configSchematics } from "./config";
import * as fs from "fs/promises";
import * as path from "path";
// ─── مسار الذاكرة التراكمية (النهائي) ───
const FINAL_MEMORY_PATH = "D:/bido/2026/final_memory";
export async function preprocess(ctl: PromptPreprocessorController, userMessage: ChatMessage) {
const userPrompt = userMessage.getText();
const history = await ctl.pullHistory();
history.append(userMessage);
// سنقوم بالبحث دائماً إذا كان المجلد موجوداً
try {
const stats = await fs.stat(FINAL_MEMORY_PATH);
if (stats.isDirectory()) {
const processedContent = await searchAndInjectMemory(ctl, userPrompt);
if (processedContent !== userPrompt) {
userMessage.replaceText(processedContent);
}
}
} catch (err) {
ctl.debug("Memory folder not found or accessible", err);
}
return userMessage;
}
function cosineSimilarity(a: number[], b: number[]) {
if (!a || !b || a.length !== b.length || a.length === 0) return 0;
let dot = 0, normA = 0, normB = 0;
for (let i = 0; i < a.length; i++) {
dot += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
return dot / (Math.sqrt(normA) * Math.sqrt(normB) + 1e-9);
}
async function searchAndInjectMemory(
ctl: PromptPreprocessorController,
originalUserPrompt: string
): Promise<string> {
const pluginConfig = ctl.getPluginConfig(configSchematics);
const retrievalLimit = pluginConfig.get("retrievalLimit") || 3;
const retrievalAffinityThreshold = pluginConfig.get("retrievalAffinityThreshold") || 0.5;
const retrievingStatus = ctl.createStatus({
status: "loading",
text: `Loading an embedding model for memory retrieval...`,
});
try {
// 1. استدعاء نموذج التضمين (Nomic)
const model = await ctl.client.embedding.model("nomic-ai/nomic-embed-text-v1.5-GGUF", {
signal: ctl.abortSignal,
});
retrievingStatus.setState({
status: "loading",
text: `Embedding user query...`,
});
// 2. تحويل سؤال المستخدم إلى Embedding
const embedResult = await model.embed(originalUserPrompt);
const userEmbedding = embedResult.embedding;
retrievingStatus.setState({
status: "loading",
text: `Searching in memory nodes...`,
});
// 3. قراءة العقد من الذاكرة
let allNodes: any[] = [];
const files = await fs.readdir(FINAL_MEMORY_PATH);
for (const file of files) {
if (!file.endsWith('.json') || file === 'template.json') continue;
const content = await fs.readFile(path.join(FINAL_MEMORY_PATH, file), 'utf-8');
try {
const data = JSON.parse(content);
if (data.nodes && Array.isArray(data.nodes)) {
allNodes = allNodes.concat(data.nodes);
}
} catch (e) {
ctl.debug(`Error parsing ${file}`);
}
}
if (allNodes.length === 0) {
retrievingStatus.setState({ status: "canceled", text: `No memory nodes found.` });
return originalUserPrompt;
}
// 4. حساب التشابه
const results = allNodes
.filter(node => node.embedding && Array.isArray(node.embedding) && node.embedding.length > 0)
.map(node => ({
...node,
similarity: cosineSimilarity(userEmbedding, node.embedding)
}))
.filter(r => r.similarity >= retrievalAffinityThreshold)
.sort((a, b) => b.similarity - a.similarity)
.slice(0, retrievalLimit);
if (results.length > 0) {
retrievingStatus.setState({
status: "done",
text: `Retrieved ${results.length} relevant memory pointers for user query`,
});
// ─── بناء رسالة المؤشرات (بدون حشو نصي) ─────────────────────────
let processedContent = `📌 **مؤشرات ذاكرة (ليست نصوصاً كاملة):**\n\n`;
results.forEach((r, idx) => {
processedContent += `- **الفكرة**: ${r.title || 'بدون عنوان'}\n`;
processedContent += ` **الملف**: ${r.filePath || r.conversation_id || 'غير معروف'}\n`;
if (r.summary) processedContent += ` **خلاصة**: ${r.summary}\n`;
if (r.key_ideas && r.key_ideas.length > 0) {
processedContent += ` **أفكار**: ${r.key_ideas.slice(0, 3).join('، ')}\n`;
}
if (r.decisions && r.decisions.length > 0) {
processedContent += ` **قرارات**: ${r.decisions.slice(0, 2).join('، ')}\n`;
}
processedContent += ` **الصلة**: ${(r.similarity * 100).toFixed(1)}%\n\n`;
});
processedContent += `---\n🔍 **تعليمات**: هذه مجرد مؤشرات. لا تملأ السياق بنصوص طويلة. استخدم أدواتك (eagleEye، readFile، memory_recall، semantic_search) لاستدعاء التفاصيل الكاملة عند الحاجة.\n\n---\n\nرسالة المستخدم الحالية:\n${originalUserPrompt}`;
return processedContent;
} else {
retrievingStatus.setState({
status: "canceled",
text: `No highly relevant memories found for user query`,
});
return originalUserPrompt;
}
} catch (err: any) {
ctl.debug("Error during memory retrieval", err);
retrievingStatus.setState({
status: "canceled",
text: `Failed to retrieve memory: ${err.message}`,
});
return originalUserPrompt;
}
}