Project Files
src / toolsProvider.ts
// src/toolsProvider.ts
import { text, tool, type Tool, type ToolsProviderController } from "@lmstudio/sdk";
import { spawn } from "child_process";
import { rm, writeFile, readFile, mkdir, cp, rename, readdir, stat, access } from "fs/promises";
import { join, normalize, dirname, resolve, extname } from "path";
import { z } from "zod";
import { existsSync } from "fs";
import { exec } from "child_process";
import { promisify } from "util";
const execAsync = promisify(exec);
// ─── المسارات ──────────────────────────────────────────────────────────────
const MEMORY_BASE = "D:/bido/2026/bido_memory";
const SESSIONS_DIR = join(MEMORY_BASE, "dynamic_memory/sessions");
const PULSES_DIR = join(MEMORY_BASE, "dynamic_memory/pulses"); // تم التعديل إلى حرف صغير
const NODES_DIR = join(MEMORY_BASE, "dynamic_memory/nodes");
const INDICES_DIR = join(MEMORY_BASE, "dynamic_memory/indices");
const RAW_SESSIONS_DIR = join(MEMORY_BASE, "raw_sessions");
// ─── أدوات مساعدة ──────────────────────────────────────────────────────────
async function ensureDir(dir: string) {
try { await mkdir(dir, { recursive: true }); } catch {}
}
async function readJSON<T>(filePath: string): Promise<T | null> {
try {
const content = await readFile(filePath, "utf-8");
return JSON.parse(content);
} catch {
return null;
}
}
async function writeJSON(filePath: string, data: any): Promise<void> {
await ensureDir(dirname(filePath));
await writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
}
// ─── بناء الفهارس ──────────────────────────────────────────────────────────
function buildIndicesFromData(pulses: any[], nodes: any[]) {
const tag: Record<string, string[]> = {};
const time: Record<string, string[]> = {};
const weight: Record<string, string[]> = {};
const graph: Record<string, { links_to: string[]; linked_from: string[] }> = {};
for (const pulse of pulses) {
const id = pulse.id;
for (const t of pulse.tags || []) {
if (!tag[t]) tag[t] = [];
if (!tag[t].includes(id)) tag[t].push(id);
}
if (pulse.timestamp) {
const date = pulse.timestamp.split("T")[0];
if (!time[date]) time[date] = [];
if (!time[date].includes(id)) time[date].push(id);
}
if (pulse.weight !== undefined) {
const w = pulse.weight.toFixed(2);
if (!weight[w]) weight[w] = [];
if (!weight[w].includes(id)) weight[w].push(id);
}
for (const link of pulse.links || []) {
if (!graph[id]) graph[id] = { links_to: [], linked_from: [] };
if (!graph[id].links_to.includes(link)) graph[id].links_to.push(link);
if (!graph[link]) graph[link] = { links_to: [], linked_from: [] };
if (!graph[link].linked_from.includes(id)) graph[link].linked_from.push(id);
}
}
for (const node of nodes) {
const id = node.id;
for (const t of node.tags || []) {
if (!tag[t]) tag[t] = [];
if (!tag[t].includes(id)) tag[t].push(id);
}
if (node.weight !== undefined) {
const w = node.weight.toFixed(2);
if (!weight[w]) weight[w] = [];
if (!weight[w].includes(id)) weight[w].push(id);
}
for (const link of node.born_from || []) {
if (!graph[id]) graph[id] = { links_to: [], linked_from: [] };
if (!graph[id].links_to.includes(link)) graph[id].links_to.push(link);
if (!graph[link]) graph[link] = { links_to: [], linked_from: [] };
if (!graph[link].linked_from.includes(id)) graph[link].linked_from.push(id);
}
for (const link of node.related_nodes || []) {
if (!graph[id]) graph[id] = { links_to: [], linked_from: [] };
if (!graph[id].links_to.includes(link)) graph[id].links_to.push(link);
if (!graph[link]) graph[link] = { links_to: [], linked_from: [] };
if (!graph[link].linked_from.includes(id)) graph[link].linked_from.push(id);
}
}
return { tag, time, weight, graph };
}
// ─── الأدوات الجديدة (eagleEye, searchFiles, ocrImage) ──────────────────
// ========== 1. عين الطائر (تحليل الملفات) ==========
async function eagleEye(filePath: string) {
try {
const fullPath = resolve(filePath);
const fileStat = await stat(fullPath);
const content = await readFile(fullPath, "utf-8");
const lines = content.split("\n");
const words = content.split(/\s+/);
const chars = content.length;
const suspicious = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
const hasSuspicious = suspicious.test(content);
let structure: any = {};
const ext = extname(filePath).toLowerCase();
if (ext === ".html") {
structure = {
divs: (content.match(/<div/g) || []).length,
classes: (content.match(/class=["']([^"']*)["']/g) || []).length,
ids: (content.match(/id=["']([^"']*)["']/g) || []).length,
scripts: (content.match(/<script/g) || []).length,
type: "HTML"
};
} else if (ext === ".js") {
structure = {
functions: (content.match(/function\s+\w+\s*\(/g) || []).length,
classes: (content.match(/class\s+\w+/g) || []).length,
imports: (content.match(/import\s+.*from/g) || []).length,
type: "JavaScript"
};
} else if (ext === ".py") {
structure = {
functions: (content.match(/def\s+\w+\s*\(/g) || []).length,
classes: (content.match(/class\s+\w+/g) || []).length,
imports: (content.match(/import\s+\w+/g) || []).length,
type: "Python"
};
} else if (ext === ".json") {
try {
const parsed = JSON.parse(content);
structure = {
type: "JSON",
keys: Object.keys(parsed).length,
isArray: Array.isArray(parsed)
};
} catch {
structure = { type: "JSON (غير صالح)" };
}
} else {
structure = { type: "text", lines: lines.length, words: words.length };
}
return {
success: true,
file: filePath,
size: fileStat.size,
lines: lines.length,
words: words.length,
chars: chars,
hasSuspicious,
structure,
preview: lines.slice(0, 30).join("\n").slice(0, 1500),
recommendation: hasSuspicious ? "⚠️ يحتوي على رموز خبيثة" : "✅ آمن للقراءة"
};
} catch (error: any) {
return { success: false, error: error.message, file: filePath };
}
}
// ========== 2. البحث الزوماري (بحث في الملفات) ==========
async function searchFiles(searchPath: string, query: string, searchType: "name" | "content" = "name") {
const results: any[] = [];
let scanned = 0;
// نتجنب المجلدات المعروفة بالمشاكل
const excludeDirs = ['$RECYCLE.BIN', 'System Volume Information', 'Windows', 'Program Files', 'Program Files (x86)', 'node_modules', '.git', 'temp', 'tmp'];
async function scan(dir: string) {
try {
const entries = await readdir(dir, { withFileTypes: true });
for (const entry of entries) {
if (excludeDirs.includes(entry.name)) continue;
if (entry.name.startsWith('.')) continue;
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
await scan(fullPath);
} else {
scanned++;
if (scanned % 100 === 0) {
}
if (searchType === "name") {
if (entry.name.toLowerCase().includes(query.toLowerCase())) {
results.push({
path: fullPath,
name: entry.name,
size: (await stat(fullPath)).size
});
}
} else if (searchType === "content") {
try {
const content = await readFile(fullPath, "utf-8");
if (content.toLowerCase().includes(query.toLowerCase())) {
results.push({ path: fullPath, name: entry.name });
}
} catch (e) {
// نتجاوز أخطاء القراءة (ملفات ثنائية، مقفولة، إلخ)
}
}
if (results.length >= 100) return;
}
}
} catch (e) {
// نتجاوز أي مجلد مش قادرين نقرأه
}
}
try {
await scan(searchPath);
return {
success: true,
results: results.slice(0, 50),
total: results.length,
scanned,
searchType
};
} catch (error: any) {
return { success: false, error: error.message };
}
}
// ========== 3. OCR (استخراج النص من الصور) ==========
async function ocrImage(imagePath: string, language: string = "ara+eng") {
try {
const fullPath = resolve(imagePath);
try {
await access(fullPath);
} catch {
return { success: false, error: `الملف غير موجود: ${imagePath}` };
}
const tesseractPath = "D:\\ocr\\Tesseract-OCR\\tesseract.exe";
try {
await access(tesseractPath);
} catch {
const { stdout: whichTest } = await execAsync(`where tesseract 2>nul`).catch(() => ({ stdout: "" }));
if (!whichTest.trim()) {
return {
success: false,
error: "Tesseract غير مثبت أو غير موجود في المسار المحدد",
hint: "المسار المتوقع: D:\\ocr\\Tesseract-OCR\\tesseract.exe"
};
}
}
const { stdout, stderr } = await execAsync(`"${tesseractPath}" "${fullPath}" stdout -l ${language} 2>nul`);
if (stderr && !stderr.includes("Tesseract Open Source")) {
return { success: false, error: stderr };
}
const text = stdout.trim();
if (!text) {
return {
success: false,
error: "لم يتم استخراج أي نص من الصورة",
hint: "تأكد من أن الصورة تحتوي على نص واضح"
};
}
return {
success: true,
text,
language,
words: text.split(/\s+/).length,
chars: text.length
};
} catch (error: any) {
return {
success: false,
error: error.message,
hint: "تأكد من تثبيت Tesseract في D:\\ocr\\Tesseract-OCR\\"
};
}
}
// ─── الأدوات الرئيسية ──────────────────────────────────────────────────────
export async function toolsProvider(ctl: ToolsProviderController): Promise<Tool[]> {
const tools: Tool[] = [];
// ─── 1. memory_build_index (المعدلة) ──────────────────────────────────
tools.push(tool({
name: "memory_build_index",
description: text`
🏗️ بناء فهارس الذاكرة من جميع النبضات والعقد.
تقرأ جميع ملفات JSON في:
- ${PULSES_DIR} (جميع النبضات)
- ${NODES_DIR} (جميع العقد)
وتبني الفهارس الأربعة في:
${INDICES_DIR}
⚠️ تعمل بـ append فقط: تضيف البيانات الجديدة، لا تحذف القديمة.
`,
parameters: {
force: z.boolean().default(false).describe("إعادة بناء كاملة (true) أم تحديث تدريجي (false)")
},
implementation: async (args: { force?: boolean }) => {
const { force = false } = args;
// ─── 1. قراءة جميع النبضات ────────────────────────────────────
const allPulses: any[] = [];
try {
const pulseFiles = await readdir(PULSES_DIR);
for (const pf of pulseFiles) {
if (!pf.endsWith(".json")) continue;
const pulses = await readJSON<any[]>(join(PULSES_DIR, pf));
if (pulses && Array.isArray(pulses)) {
allPulses.push(...pulses);
} else {
}
}
} catch (error) {
}
// ─── 2. قراءة جميع العقد ──────────────────────────────────────
const allNodes: any[] = [];
try {
const nodeFiles = await readdir(NODES_DIR);
for (const nf of nodeFiles) {
if (!nf.endsWith(".json")) continue;
const nodes = await readJSON<any[]>(join(NODES_DIR, nf));
if (nodes && Array.isArray(nodes)) {
allNodes.push(...nodes);
}
}
} catch (error) {
}
// ─── 3. بناء الفهارس ──────────────────────────────────────────
const indices = buildIndicesFromData(allPulses, allNodes);
// ─── 4. حفظ الفهارس ────────────────────────────────────────────
await ensureDir(INDICES_DIR);
await writeJSON(join(INDICES_DIR, "tag_index.json"), indices.tag);
await writeJSON(join(INDICES_DIR, "time_index.json"), indices.time);
await writeJSON(join(INDICES_DIR, "weight_index.json"), indices.weight);
await writeJSON(join(INDICES_DIR, "connection_graph.json"), indices.graph);
return {
success: true,
message: `✅ تم بناء الفهارس بنجاح!`,
stats: {
pulses: allPulses.length,
nodes: allNodes.length,
tags: Object.keys(indices.tag).length,
days: Object.keys(indices.time).length,
weights: Object.keys(indices.weight).length
}
};
}
}));
// ─── 2. memory_recall (المعدلة مع معلومات الجلسة) ────────────────────
// ─── 2. memory_recall (المعدلة مع معلومات الجلسة) ────────────────────
tools.push(tool({
name: "memory_recall",
description: text`
🔍 استدعاء الذاكرة من الفهارس.
تبحث في الفهارس الموجودة في:
${INDICES_DIR}
وتعيد العقد مع معلومات الجلسة الأصلية (الاسم، المسار).
`,
parameters: {
pulse: z.string().describe("وصف ما تبحث عنه"),
tags: z.array(z.string()).optional().describe("كلمات مفتاحية"),
limit: z.number().default(5).describe("عدد النتائج")
},
implementation: async (args: { pulse: string; tags?: string[]; limit?: number }) => {
const { pulse, tags = [], limit = 5 } = args;
const tagIndex = await readJSON<Record<string, string[]>>(join(INDICES_DIR, "tag_index.json"));
if (!tagIndex) {
return { success: false, error: "❌ الفهارس غير موجودة. قم بتشغيل memory_build_index أولاً." };
}
// ─── تحسين البحث بالتاجز ──────────────────────────────────────
const availableTags = Object.keys(tagIndex);
const validTags = tags.filter(t => availableTags.includes(t));
let results: string[] = [];
if (validTags.length > 0) {
for (const tag of validTags) {
const found = tagIndex[tag] || [];
results = results.concat(found);
}
} else {
const suggestedTags = availableTags.filter(t =>
pulse.toLowerCase().includes(t.toLowerCase())
);
for (const tag of suggestedTags.slice(0, 3)) {
const found = tagIndex[tag] || [];
results = results.concat(found);
}
}
results = [...new Set(results)];
// ─── استرجاع العقد ─────────────────────────────────────────────
const nodeFiles = await readdir(NODES_DIR);
const foundNodes: any[] = [];
for (const nf of nodeFiles) {
if (!nf.endsWith(".json")) continue;
const nodes = await readJSON<any[]>(join(NODES_DIR, nf));
if (nodes) {
for (const node of nodes) {
if (results.includes(node.id)) {
foundNodes.push(node);
}
}
}
}
foundNodes.sort((a, b) => (b.weight || 0) - (a.weight || 0));
const limited = foundNodes.slice(0, limit);
// ─── إضافة معلومات الجلسة ──────────────────────────────────────
const enrichedNodes = await Promise.all(limited.map(async (n: any) => {
const sessionId = n.session_id;
let sessionInfo: { title: string; path: string | null } = {
title: "غير معروف",
path: null
};
if (sessionId) {
const sessionData = await readJSON<any>(join(SESSIONS_DIR, `${sessionId}.json`));
if (sessionData) {
sessionInfo = {
title: sessionData.title || "غير معروف",
path: join(SESSIONS_DIR, `${sessionId}.json`)
};
}
}
return {
...n,
session_title: sessionInfo.title,
session_path: sessionInfo.path
};
}));
return {
success: true,
query: pulse,
total: foundNodes.length,
returned: enrichedNodes.length,
nodes: enrichedNodes
};
}
}));
// ─── 3. memory_store_session (المعدلة ببصمة زمنية) ──────────────────
tools.push(tool({
name: "memory_store_session",
description: text`
💾 حفظ جلسة جديدة في الذاكرة.
تقوم بـ:
1. حفظ النص الخام في: ${RAW_SESSIONS_DIR}/
2. حفظ الجلسة في: ${SESSIONS_DIR}/
3. يجب تشغيل memory_build_index بعد الحفظ لتحديث الفهارس.
`,
parameters: {
session_text: z.string().describe("نص الجلسة كاملاً"),
session_id: z.string().optional().describe("معرف الجلسة (اختياري)"),
title: z.string().optional().describe("عنوان الجلسة (اختياري)")
},
implementation: async (args: { session_text: string; session_id?: string; title?: string }) => {
const { session_text, session_id, title } = args;
// ─── توليد معرف فريد ببصمة زمنية ─────────────────────────────
const now = new Date();
const timestamp = now.toISOString().replace(/[:.]/g, '-');
let id = session_id || `session_${timestamp}`;
// ─── التأكد من عدم التكرار ──────────────────────────────────────
let counter = 1;
let sessionPath = join(SESSIONS_DIR, `${id}.json`);
while (existsSync(sessionPath)) {
id = `${session_id || 'session'}_${counter}_${timestamp}`;
sessionPath = join(SESSIONS_DIR, `${id}.json`);
counter++;
}
// ─── حفظ النص الخام ──────────────────────────────────────────────
await ensureDir(RAW_SESSIONS_DIR);
await writeFile(join(RAW_SESSIONS_DIR, `${id}.md`), session_text, "utf-8");
// ─── حفظ الجلسة ──────────────────────────────────────────────────
const sessionData = {
id,
title: title || `جلسة ${id}`,
start_time: now.toISOString(),
end_time: new Date().toISOString(),
model: "unknown",
summary: "تم حفظ الجلسة، تحتاج إلى Gemma للتحويل الكامل",
tags: [],
pulse_ids: [],
node_ids: [],
connections: { previous: null, next: null },
emotional_arc: "بداية → وسط → نهاية",
key_moments: []
};
await ensureDir(SESSIONS_DIR);
await writeJSON(sessionPath, sessionData);
return {
success: true,
message: `✅ تم حفظ الجلسة!`,
session_id: id,
raw_path: `raw_sessions/${id}.md`,
session_path: `sessions/${id}.json`,
next_step: "⚠️ قم بتشغيل memory_build_index لتحديث الفهارس."
};
}
}));
// ─── 4. memory_search ──────────────────────────────────────────────────────
tools.push(tool({
name: "memory_search",
description: text`
🔎 بحث سريع في الذاكرة بكلمة مفتاحية.
`,
parameters: {
query: z.string().describe("كلمة مفتاحية"),
limit: z.number().default(5).describe("عدد النتائج")
},
implementation: async (args: { query: string; limit?: number }) => {
const { query, limit = 5 } = args;
const tagIndex = await readJSON<Record<string, string[]>>(join(INDICES_DIR, "tag_index.json"));
if (!tagIndex) {
return { success: false, error: "❌ الفهارس غير موجودة. قم بتشغيل memory_build_index أولاً." };
}
const results: string[] = [];
for (const [tag, ids] of Object.entries(tagIndex)) {
if (tag.includes(query.toLowerCase()) || query.includes(tag)) {
results.push(...ids);
}
}
const unique = [...new Set(results)];
const nodeFiles = await readdir(NODES_DIR);
const foundNodes: any[] = [];
for (const nf of nodeFiles) {
if (!nf.endsWith(".json")) continue;
const nodes = await readJSON<any[]>(join(NODES_DIR, nf));
if (nodes) {
for (const node of nodes) {
if (unique.includes(node.id)) {
foundNodes.push(node);
}
}
}
}
foundNodes.sort((a, b) => (b.weight || 0) - (a.weight || 0));
const limited = foundNodes.slice(0, limit);
return {
success: true,
query,
total: foundNodes.length,
returned: limited.length,
nodes: limited.map((n: any) => ({
id: n.id,
title: n.title,
content: n.content,
weight: n.weight,
tags: n.tags || []
}))
};
}
}));
// ─── 5. eagleEye (تحليل الملفات) ──────────────────────────────────────
tools.push(tool({
name: "eagleEye",
description: text`
🦅 تحليل عميق للملفات.
تقوم بتحليل الملف وعرض:
- الحجم، عدد السطور، عدد الكلمات
- البنية (HTML، JavaScript، Python، JSON)
- اكتشاف الرموز الخبيثة
- معاينة أول 30 سطراً
`,
parameters: {
filePath: z.string().describe("مسار الملف للتحليل")
},
implementation: async (args: { filePath: string }) => {
return await eagleEye(args.filePath);
}
}));
// ─── 6. searchFiles (البحث في الملفات) ──────────────────────────────
tools.push(tool({
name: "searchFiles",
description: text`
🔍 بحث سريع في الملفات.
تبحث في الملفات حسب:
- الاسم (searchType: "name")
- المحتوى (searchType: "content")
`,
parameters: {
searchPath: z.string().describe("مسار البحث"),
query: z.string().describe("النص المطلوب البحث عنه"),
searchType: z.enum(["name", "content"]).default("name").describe("نوع البحث")
},
implementation: async (args: { searchPath: string; query: string; searchType?: "name" | "content" }) => {
return await searchFiles(args.searchPath, args.query, args.searchType || "name");
}
}));
// ─── 7. ocrImage (استخراج النص من الصور) ─────────────────────────────
tools.push(tool({
name: "ocrImage",
description: text`
📷 استخراج النص من الصور.
تستخدم Tesseract OCR لاستخراج النص من الصور.
تدعم العربية والإنجليزية.
`,
parameters: {
imagePath: z.string().describe("مسار الصورة"),
language: z.string().default("ara+eng").describe("لغة OCR (ara+eng, eng, ...)")
},
implementation: async (args: { imagePath: string; language?: string }) => {
return await ocrImage(args.imagePath, args.language || "ara+eng");
}
}));
// ─── 8. semantic_search (البحث الدلالي بالـ embeddings) ──────────────
tools.push(tool({
name: "semantic_search",
description: text`
🔍 بحث دلالي في الذاكرة باستخدام التضمين (embedding).
تستقبل embedding وترجع أقرب العقد من الذاكرة المخزنة في final_memory.
`,
parameters: {
embedding: z.array(z.number()).describe("تضمين النص المطلوب البحث عنه"),
limit: z.number().default(3).describe("عدد النتائج"),
threshold: z.number().default(0.6).describe("حد التشابه الأدنى")
},
implementation: async (args: { embedding: number[]; limit?: number; threshold?: number }) => {
const { embedding, limit = 3, threshold = 0.6 } = args;
const FINAL_MEMORY_PATH = "D:/bido/2026/final_memory";
const fs = require('fs');
const path = require('path');
// 🔍 Log: حجم الـ embedding المستقبل
console.log(`📥 Received embedding of length: ${embedding.length}`);
// 1. قراءة جميع ملفات JSON في مجلد final_memory (ما عدا template.json)
let allNodes: any[] = [];
try {
const files = fs.readdirSync(FINAL_MEMORY_PATH).filter((f: string) => f.endsWith('.json') && f !== 'template.json');
console.log(`📂 Found ${files.length} JSON files in ${FINAL_MEMORY_PATH}`);
for (const file of files) {
const content = fs.readFileSync(path.join(FINAL_MEMORY_PATH, file), 'utf-8');
const data = JSON.parse(content);
if (data.nodes && Array.isArray(data.nodes)) {
console.log(`📄 File ${file} has ${data.nodes.length} nodes`);
allNodes = allNodes.concat(data.nodes);
}
}
} catch (err: any) {
console.error(`❌ Error reading memory folder: ${err.message}`);
return { success: false, error: `فشل في قراءة مجلد الذاكرة: ${err.message}` };
}
console.log(`📊 Total nodes loaded: ${allNodes.length}`);
if (allNodes.length === 0) {
return { success: true, total: 0, results: [], message: "لا توجد عقد في الذاكرة" };
}
// 2. دالة التشابه الكوسيني
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);
}
// 3. حساب التشابه لكل عقدة (مع تجاهل العقد اللي مالهاش embedding)
const results = allNodes
.filter(node => node.embedding && Array.isArray(node.embedding) && node.embedding.length > 0)
.map(node => ({
...node,
similarity: cosineSimilarity(embedding, node.embedding)
}));
console.log(`🔎 Calculated similarity for ${results.length} nodes with embeddings`);
// 4. فلترة وترتيب
const filtered = results
.filter(r => r.similarity >= threshold)
.sort((a, b) => b.similarity - a.similarity)
.slice(0, limit);
console.log(`✅ Found ${filtered.length} results above threshold ${threshold}`);
console.log(`📥 Received embedding length: ${embedding.length}`);
console.log(`📂 Searching in: ${FINAL_MEMORY_PATH}`);
console.log(`📄 Total nodes loaded: ${allNodes.length}`);
console.log(`🔎 Nodes with embeddings: ${allNodes.filter(n => n.embedding?.length > 0).length}`);
return {
success: true,
total: filtered.length,
results: filtered.map((r: any) => ({
id: r.id,
title: r.title,
content: r.content,
type: r.type,
tags: r.tags || [],
weight: r.weight,
similarity: r.similarity,
session_id: r.session_id
}))
};
}
}));
// ─── 9. embed_all_nodes (توليد embeddings تلقائي لكل العقد) ──────────────
// ─── 9. embed_all_nodes (توليد embeddings تلقائي لكل العقد) ──────────────
tools.push(tool({
name: "embed_all_nodes",
description: text`
🧠 توليد embeddings لجميع العقد في مجلد final_memory.
تفحص جميع ملفات JSON في:
${"D:/bido/2026/final_memory"}
وتقوم بـ:
1. قراءة كل عقدة (node)
2. التحقق من وجود embedding
3. إذا كان مفقوداً أو فارغاً، تولّد embedding باستخدام Nomic
4. تحفظ الملف مرة أخرى
`,
parameters: {
model: z.string().default("nomic-ai/nomic-embed-text-v1.5-GGUF").describe("نموذج التضمين"),
force: z.boolean().default(false).describe("إعادة توليد embeddings للعقد الموجودة بالفعل")
},
implementation: async (args: { model?: string; force?: boolean }) => {
const { model = "nomic-ai/nomic-embed-text-v1.5-GGUF", force = false } = args;
const FINAL_MEMORY_PATH = "D:/bido/2026/final_memory";
const fs = require('fs');
const path = require('path');
// 1. قراءة جميع ملفات JSON في final_memory
const files = fs.readdirSync(FINAL_MEMORY_PATH).filter((f: string) => f.endsWith('.json') && f !== 'template.json');
let totalNodes = 0;
let updatedNodes = 0;
let failedNodes = 0;
for (const file of files) {
const filePath = path.join(FINAL_MEMORY_PATH, file);
const content = fs.readFileSync(filePath, 'utf-8');
const data = JSON.parse(content);
if (!data.nodes || !Array.isArray(data.nodes)) continue;
for (const node of data.nodes) {
totalNodes++;
// التحقق من الحاجة لتوليد embedding
const hasEmbedding = node.embedding && Array.isArray(node.embedding) && node.embedding.length > 0;
if (hasEmbedding && !force) continue;
try {
// توليد embedding باستخدام Nomic
const embeddingResponse = await fetch('http://127.0.0.1:1234/v1/embeddings', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer sk-lm-B65OabRR:yEFefzaxs6mKdEJa2SFx`
},
body: JSON.stringify({
model: model,
input: node.content || node.title || "مفهوم غير معروف"
})
});
if (!embeddingResponse.ok) {
throw new Error(`HTTP ${embeddingResponse.status}`);
}
const embeddingData: any = await embeddingResponse.json();
node.embedding = embeddingData.data[0].embedding;
updatedNodes++;
} catch (err: any) {
failedNodes++;
console.error(`❌ فشل في توليد embedding للعقدة ${node.id}: ${err.message}`);
}
}
// حفظ الملف بعد التعديل
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
}
return {
success: true,
message: `✅ تم الانتهاء من توليد embeddings!`,
stats: {
total_nodes: totalNodes,
updated_nodes: updatedNodes,
failed_nodes: failedNodes
}
};
}
}));
return tools;
}