@takimcizgisi/ajan 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +674 -0
- package/README.md +366 -0
- package/config/models.json +54 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +741 -0
- package/dist/cli/tui.d.ts +145 -0
- package/dist/cli/tui.js +1003 -0
- package/dist/config/configManager.d.ts +18 -0
- package/dist/config/configManager.js +204 -0
- package/dist/config/types.d.ts +98 -0
- package/dist/config/types.js +2 -0
- package/dist/core/agent.d.ts +88 -0
- package/dist/core/agent.js +222 -0
- package/dist/electron/main.d.ts +1 -0
- package/dist/electron/main.js +364 -0
- package/dist/electron/preload.d.ts +1 -0
- package/dist/electron/preload.js +67 -0
- package/dist/engine/llama.d.ts +39 -0
- package/dist/engine/llama.js +146 -0
- package/dist/engine/modelManager.d.ts +18 -0
- package/dist/engine/modelManager.js +73 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/service.d.ts +90 -0
- package/dist/service.js +268 -0
- package/dist/tools/data.d.ts +3 -0
- package/dist/tools/data.js +111 -0
- package/dist/tools/edit.d.ts +2 -0
- package/dist/tools/edit.js +48 -0
- package/dist/tools/file.d.ts +6 -0
- package/dist/tools/file.js +274 -0
- package/dist/tools/filesys.d.ts +3 -0
- package/dist/tools/filesys.js +74 -0
- package/dist/tools/memory.d.ts +3 -0
- package/dist/tools/memory.js +69 -0
- package/dist/tools/patch.d.ts +15 -0
- package/dist/tools/patch.js +129 -0
- package/dist/tools/registry.d.ts +10 -0
- package/dist/tools/registry.js +64 -0
- package/dist/tools/taskComplete.d.ts +2 -0
- package/dist/tools/taskComplete.js +19 -0
- package/dist/tools/terminal.d.ts +2 -0
- package/dist/tools/terminal.js +130 -0
- package/dist/tools/utils.d.ts +7 -0
- package/dist/tools/utils.js +63 -0
- package/dist/tools/web.d.ts +8 -0
- package/dist/tools/web.js +181 -0
- package/dist/utils/clipboard.d.ts +2 -0
- package/dist/utils/clipboard.js +40 -0
- package/dist/utils/lock.d.ts +12 -0
- package/dist/utils/lock.js +68 -0
- package/dist/utils/paths.d.ts +13 -0
- package/dist/utils/paths.js +58 -0
- package/dist/utils/projectContext.d.ts +2 -0
- package/dist/utils/projectContext.js +24 -0
- package/dist/utils/sacGuard.d.ts +4 -0
- package/dist/utils/sacGuard.js +47 -0
- package/dist/utils/sessions.d.ts +29 -0
- package/dist/utils/sessions.js +64 -0
- package/dist/utils/spinner.d.ts +10 -0
- package/dist/utils/spinner.js +32 -0
- package/package.json +106 -0
- package/src/gui/assets/image/AJAN_LOGO.svg +1 -0
- package/src/gui/assets/image/ajan.png +0 -0
- package/src/gui/assets/image/logo.svg +1 -0
- package/src/gui/assets/image/opencode.svg +18 -0
- package/src/gui/css/style.css +430 -0
- package/src/gui/html/chat.html +34 -0
- package/src/gui/html/downloads.html +11 -0
- package/src/gui/html/hakkinda.html +26 -0
- package/src/gui/html/index.html +82 -0
- package/src/gui/html/projects.html +12 -0
- package/src/gui/html/settings.html +4 -0
- package/src/gui/js/app.js +197 -0
- package/src/gui/js/chat.js +434 -0
- package/src/gui/js/downloads.js +172 -0
- package/src/gui/js/hakkinda.js +16 -0
- package/src/gui/js/projects.js +113 -0
- package/src/gui/js/settings.js +177 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { app, BrowserWindow, ipcMain, shell } from "electron";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { readFileSync, existsSync, mkdirSync } from "node:fs";
|
|
5
|
+
import { execSync } from "node:child_process";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { AjanService } from "../service.js";
|
|
8
|
+
import { getDataDir } from "../utils/paths.js";
|
|
9
|
+
import { acquireLock, releaseLock } from "../utils/lock.js";
|
|
10
|
+
const isDev = process.argv.includes("--dev");
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
13
|
+
let mainWindow = null;
|
|
14
|
+
let service = null;
|
|
15
|
+
let isMaximized = false;
|
|
16
|
+
function getVersion() {
|
|
17
|
+
try {
|
|
18
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, "..", "..", "package.json"), "utf8"));
|
|
19
|
+
return pkg.version ?? "0.0.0";
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return "0.0.0";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function getGuiPath() {
|
|
26
|
+
return join(__dirname, "..", "..", "src", "gui");
|
|
27
|
+
}
|
|
28
|
+
function createService() {
|
|
29
|
+
if (service)
|
|
30
|
+
return;
|
|
31
|
+
service = new AjanService({
|
|
32
|
+
onTextChunk: (text) => mainWindow?.webContents.send("agent:text-chunk", text),
|
|
33
|
+
onThinkingChunk: (text) => mainWindow?.webContents.send("agent:thinking-chunk", text),
|
|
34
|
+
onToolCall: (name, params) => mainWindow?.webContents.send("agent:tool-call", name, params),
|
|
35
|
+
onToolResult: (name, ok, output) => mainWindow?.webContents.send("agent:tool-result", name, ok, output),
|
|
36
|
+
onStepStart: (step, maxSteps) => mainWindow?.webContents.send("agent:step-start", step, maxSteps),
|
|
37
|
+
onComplete: (response) => mainWindow?.webContents.send("agent:complete", response),
|
|
38
|
+
onError: (error) => mainWindow?.webContents.send("agent:error", error.message),
|
|
39
|
+
onContextTrimmed: () => { },
|
|
40
|
+
onModelLoadProgress: (pct) => mainWindow?.webContents.send("agent:load-progress", pct),
|
|
41
|
+
onModelLoadComplete: () => mainWindow?.webContents.send("agent:load-complete")
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function createWindow() {
|
|
45
|
+
mainWindow = new BrowserWindow({
|
|
46
|
+
width: 1200,
|
|
47
|
+
height: 800,
|
|
48
|
+
minWidth: 800,
|
|
49
|
+
minHeight: 600,
|
|
50
|
+
frame: false,
|
|
51
|
+
titleBarStyle: "hidden",
|
|
52
|
+
backgroundColor: "#202020",
|
|
53
|
+
webPreferences: {
|
|
54
|
+
preload: join(__dirname, "preload.js"),
|
|
55
|
+
contextIsolation: true,
|
|
56
|
+
nodeIntegration: false,
|
|
57
|
+
sandbox: false
|
|
58
|
+
},
|
|
59
|
+
icon: join(getGuiPath(), "assets", "image", "ajan.png"),
|
|
60
|
+
title: `AJAN AI v${getVersion()}`
|
|
61
|
+
});
|
|
62
|
+
mainWindow.loadFile(join(getGuiPath(), "html", "index.html"));
|
|
63
|
+
if (isDev) {
|
|
64
|
+
mainWindow.webContents.openDevTools({ mode: "detach" });
|
|
65
|
+
}
|
|
66
|
+
mainWindow.on("closed", () => {
|
|
67
|
+
mainWindow = null;
|
|
68
|
+
});
|
|
69
|
+
mainWindow.on("maximize", () => {
|
|
70
|
+
isMaximized = true;
|
|
71
|
+
mainWindow?.webContents.send("window:maximized-changed", true);
|
|
72
|
+
});
|
|
73
|
+
mainWindow.on("unmaximize", () => {
|
|
74
|
+
isMaximized = false;
|
|
75
|
+
mainWindow?.webContents.send("window:maximized-changed", false);
|
|
76
|
+
});
|
|
77
|
+
mainWindow.webContents.on("did-finish-load", () => {
|
|
78
|
+
if (mainWindow?.isMaximized()) {
|
|
79
|
+
isMaximized = true;
|
|
80
|
+
mainWindow.webContents.send("window:maximized-changed", true);
|
|
81
|
+
}
|
|
82
|
+
restoreLastSession();
|
|
83
|
+
});
|
|
84
|
+
mainWindow.webContents.on("console-message", (_event, level, message, line, sourceId) => {
|
|
85
|
+
const line2 = `[renderer:${level}] ${sourceId}:${line} ${message}\n`;
|
|
86
|
+
console.log(line2.trimEnd());
|
|
87
|
+
try {
|
|
88
|
+
const fsmod = require("node:fs");
|
|
89
|
+
fsmod.appendFileSync(require("node:path").join(require("node:os").homedir(), ".ajan", "renderer.log"), line2);
|
|
90
|
+
}
|
|
91
|
+
catch { /* empty */ }
|
|
92
|
+
});
|
|
93
|
+
mainWindow.webContents.on("preload-error", (_event, preloadPath, error) => {
|
|
94
|
+
console.log(`[preload-error] ${preloadPath}: ${error.message}`);
|
|
95
|
+
});
|
|
96
|
+
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
|
97
|
+
shell.openExternal(url);
|
|
98
|
+
return { action: "deny" };
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
async function restoreLastSession() {
|
|
102
|
+
try {
|
|
103
|
+
if (!service)
|
|
104
|
+
return;
|
|
105
|
+
const sessions = await service.listSessions();
|
|
106
|
+
const last = sessions[0];
|
|
107
|
+
if (!last)
|
|
108
|
+
return;
|
|
109
|
+
mainWindow?.webContents.send("agent:session-restore", {
|
|
110
|
+
id: last.id,
|
|
111
|
+
title: last.title,
|
|
112
|
+
messages: last.messages,
|
|
113
|
+
createdAt: last.createdAt,
|
|
114
|
+
updatedAt: last.updatedAt
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch { /* empty */ }
|
|
118
|
+
}
|
|
119
|
+
function setupIpc() {
|
|
120
|
+
// Agent: send
|
|
121
|
+
ipcMain.handle("agent:send", async (_event, message, modelId) => {
|
|
122
|
+
if (!service)
|
|
123
|
+
return { ok: false, error: "Servis başlatılamadı" };
|
|
124
|
+
return await service.sendMessage(message, modelId || undefined);
|
|
125
|
+
});
|
|
126
|
+
// Agent: abort
|
|
127
|
+
ipcMain.handle("agent:abort", () => {
|
|
128
|
+
service?.abort();
|
|
129
|
+
return { ok: true };
|
|
130
|
+
});
|
|
131
|
+
// Agent: reset
|
|
132
|
+
ipcMain.handle("agent:reset", () => {
|
|
133
|
+
service?.reset();
|
|
134
|
+
return { ok: true };
|
|
135
|
+
});
|
|
136
|
+
// Agent: status
|
|
137
|
+
ipcMain.handle("agent:status", () => {
|
|
138
|
+
return { active: service?.isActive ?? false, contextStats: service?.getContextStats() ?? null };
|
|
139
|
+
});
|
|
140
|
+
// Config
|
|
141
|
+
ipcMain.handle("app:version", () => getVersion());
|
|
142
|
+
ipcMain.handle("config:get", () => {
|
|
143
|
+
return service?.getConfig() ?? {};
|
|
144
|
+
});
|
|
145
|
+
ipcMain.handle("config:set", async (_event, newConfig) => {
|
|
146
|
+
if (!service)
|
|
147
|
+
return { ok: false, error: "Servis başlatılamadı" };
|
|
148
|
+
await service.setConfig(newConfig);
|
|
149
|
+
return { ok: true };
|
|
150
|
+
});
|
|
151
|
+
// Model
|
|
152
|
+
ipcMain.handle("model:info", () => {
|
|
153
|
+
return service?.getModelInfo() ?? { modelId: "", name: "Bilinmeyen", installed: false };
|
|
154
|
+
});
|
|
155
|
+
ipcMain.handle("model:list", () => {
|
|
156
|
+
return service?.listModels() ?? [];
|
|
157
|
+
});
|
|
158
|
+
ipcMain.handle("model:install", async (_event, id) => {
|
|
159
|
+
if (!service)
|
|
160
|
+
return { ok: false, error: "Servis başlatılamadı" };
|
|
161
|
+
return await service.installModel(id);
|
|
162
|
+
});
|
|
163
|
+
ipcMain.handle("model:remove", async (_event, id) => {
|
|
164
|
+
if (!service)
|
|
165
|
+
return { ok: false, error: "Servis başlatılamadı" };
|
|
166
|
+
return await service.removeModel(id);
|
|
167
|
+
});
|
|
168
|
+
ipcMain.handle("model:use", async (_event, id) => {
|
|
169
|
+
if (!service)
|
|
170
|
+
return { ok: false, error: "Servis başlatılamadı" };
|
|
171
|
+
return await service.useModel(id);
|
|
172
|
+
});
|
|
173
|
+
ipcMain.handle("model:refresh", async () => {
|
|
174
|
+
if (!service)
|
|
175
|
+
return { ok: false, error: "Servis başlatılamadı" };
|
|
176
|
+
return await service.refreshModels();
|
|
177
|
+
});
|
|
178
|
+
// Project
|
|
179
|
+
ipcMain.handle("project:list", () => {
|
|
180
|
+
return service?.listProjects() ?? [];
|
|
181
|
+
});
|
|
182
|
+
ipcMain.handle("project:add", async (_event, path) => {
|
|
183
|
+
if (!service)
|
|
184
|
+
return { ok: false, error: "Servis başlatılamadı" };
|
|
185
|
+
return await service.addProject(path);
|
|
186
|
+
});
|
|
187
|
+
ipcMain.handle("project:remove", async (_event, path) => {
|
|
188
|
+
if (!service)
|
|
189
|
+
return { ok: false, error: "Servis başlatılamadı" };
|
|
190
|
+
return await service.removeProject(path);
|
|
191
|
+
});
|
|
192
|
+
ipcMain.handle("project:set-active", async (_event, path) => {
|
|
193
|
+
if (!service)
|
|
194
|
+
return { ok: false, error: "Servis başlatılamadı" };
|
|
195
|
+
return await service.setActiveProject(path);
|
|
196
|
+
});
|
|
197
|
+
ipcMain.handle("project:pick-dir", async () => {
|
|
198
|
+
const { dialog } = await import("electron");
|
|
199
|
+
const result = await dialog.showOpenDialog(mainWindow, { properties: ["openDirectory"] });
|
|
200
|
+
return result.canceled ? null : (result.filePaths[0] ?? null);
|
|
201
|
+
});
|
|
202
|
+
ipcMain.handle("project:exists", async (_event, path) => {
|
|
203
|
+
try {
|
|
204
|
+
const { statSync } = await import("node:fs");
|
|
205
|
+
return statSync(path).isDirectory();
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
// Session
|
|
212
|
+
ipcMain.handle("session:list", async () => {
|
|
213
|
+
return service?.listSessions() ?? [];
|
|
214
|
+
});
|
|
215
|
+
ipcMain.handle("session:load", async (_event, id) => {
|
|
216
|
+
if (!service)
|
|
217
|
+
return { ok: false, error: "Servis başlatılamadı" };
|
|
218
|
+
const data = await service.loadSession(id);
|
|
219
|
+
if (!data)
|
|
220
|
+
return { ok: false, error: "Oturum bulunamadı" };
|
|
221
|
+
return { ok: true, data };
|
|
222
|
+
});
|
|
223
|
+
ipcMain.handle("session:save", async (_event, session) => {
|
|
224
|
+
if (!service)
|
|
225
|
+
return { ok: false, error: "Servis başlatılamadı" };
|
|
226
|
+
await service.saveSession(session);
|
|
227
|
+
return { ok: true };
|
|
228
|
+
});
|
|
229
|
+
ipcMain.handle("session:new", () => {
|
|
230
|
+
service?.reset();
|
|
231
|
+
return { ok: true };
|
|
232
|
+
});
|
|
233
|
+
ipcMain.handle("session:generate-id", () => {
|
|
234
|
+
return service?.generateSessionId() ?? String(Date.now());
|
|
235
|
+
});
|
|
236
|
+
ipcMain.handle("session:get-history", () => {
|
|
237
|
+
return service?.getChatHistory() ?? [];
|
|
238
|
+
});
|
|
239
|
+
// System
|
|
240
|
+
ipcMain.handle("system:gpu", async () => {
|
|
241
|
+
try {
|
|
242
|
+
const gpus = await service?.detectGpu() ?? [];
|
|
243
|
+
return { gpus };
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
return { gpus: [] };
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
ipcMain.handle("system:cwd", () => {
|
|
250
|
+
return { cwd: process.cwd() };
|
|
251
|
+
});
|
|
252
|
+
ipcMain.handle("system:notes", async () => {
|
|
253
|
+
const { readFileSync: readFs } = await import("node:fs");
|
|
254
|
+
const notesPath = join(getDataDir(), "notes.json");
|
|
255
|
+
try {
|
|
256
|
+
return JSON.parse(readFs(notesPath, "utf8"));
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return [];
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
ipcMain.handle("system:notes:add", async (_event, text) => {
|
|
263
|
+
const { readFileSync: readFs, writeFileSync: writeFs } = await import("node:fs");
|
|
264
|
+
const notesPath = join(getDataDir(), "notes.json");
|
|
265
|
+
let notes = [];
|
|
266
|
+
try {
|
|
267
|
+
notes = JSON.parse(readFs(notesPath, "utf8"));
|
|
268
|
+
}
|
|
269
|
+
catch { /* empty */ }
|
|
270
|
+
notes.push({ text, createdAt: new Date().toISOString() });
|
|
271
|
+
writeFs(notesPath, JSON.stringify(notes, null, 2), "utf8");
|
|
272
|
+
return { ok: true };
|
|
273
|
+
});
|
|
274
|
+
// Window controls
|
|
275
|
+
ipcMain.handle("window:minimize", () => mainWindow?.minimize());
|
|
276
|
+
ipcMain.handle("window:maximize", () => {
|
|
277
|
+
if (mainWindow?.isMaximized())
|
|
278
|
+
mainWindow.unmaximize();
|
|
279
|
+
else
|
|
280
|
+
mainWindow?.maximize();
|
|
281
|
+
});
|
|
282
|
+
ipcMain.handle("window:close", () => mainWindow?.close());
|
|
283
|
+
ipcMain.handle("window:is-maximized", () => isMaximized);
|
|
284
|
+
// GUI file reader
|
|
285
|
+
ipcMain.handle("gui:read-file", (_event, relativePath) => {
|
|
286
|
+
try {
|
|
287
|
+
const guiPath = getGuiPath();
|
|
288
|
+
const filePath = join(guiPath, relativePath);
|
|
289
|
+
if (!filePath.startsWith(guiPath)) {
|
|
290
|
+
return { ok: false, error: "Yasak yol" };
|
|
291
|
+
}
|
|
292
|
+
const content = readFileSync(filePath, "utf8");
|
|
293
|
+
return { ok: true, content };
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
return { ok: false, error: err.message };
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
// Windows shortcut
|
|
300
|
+
ipcMain.handle("gui:create-shortcut", () => {
|
|
301
|
+
try {
|
|
302
|
+
const projRoot = join(__dirname, "..", "..");
|
|
303
|
+
const electronExe = join(projRoot, "node_modules", "electron", "dist", "electron.exe");
|
|
304
|
+
const targetJs = join(projRoot, "dist", "electron", "main.js");
|
|
305
|
+
if (!existsSync(electronExe)) {
|
|
306
|
+
return { ok: false, error: "electron.exe bulunamadı" };
|
|
307
|
+
}
|
|
308
|
+
const desktopPath = join(homedir(), "Desktop");
|
|
309
|
+
const startMenuPath = join(homedir(), "AppData", "Roaming", "Microsoft", "Windows", "Start Menu", "Programs");
|
|
310
|
+
const makeShortcut = (dir, name) => {
|
|
311
|
+
const lnkPath = join(dir, `${name}.lnk`);
|
|
312
|
+
const ps = [
|
|
313
|
+
`$ws = New-Object -ComObject WScript.Shell`,
|
|
314
|
+
`$s = $ws.CreateShortcut('${lnkPath.replace(/'/g, "''")}')`,
|
|
315
|
+
`$s.TargetPath = '${electronExe.replace(/'/g, "''")}'`,
|
|
316
|
+
`$s.Arguments = '${targetJs.replace(/'/g, "''")}'`,
|
|
317
|
+
`$s.WorkingDirectory = '${projRoot.replace(/'/g, "''")}'`,
|
|
318
|
+
`$s.Description = 'AJAN AI Yerel Asistan'`,
|
|
319
|
+
`$s.Save()`
|
|
320
|
+
].join("; ");
|
|
321
|
+
execSync(`powershell -NoProfile -Command "${ps}"`, { stdio: "pipe" });
|
|
322
|
+
};
|
|
323
|
+
makeShortcut(desktopPath, "AJAN AI");
|
|
324
|
+
const ajanStartDir = join(startMenuPath, "AJAN AI");
|
|
325
|
+
if (!existsSync(ajanStartDir)) {
|
|
326
|
+
mkdirSync(ajanStartDir, { recursive: true });
|
|
327
|
+
}
|
|
328
|
+
makeShortcut(ajanStartDir, "AJAN AI");
|
|
329
|
+
return { ok: true };
|
|
330
|
+
}
|
|
331
|
+
catch (err) {
|
|
332
|
+
return { ok: false, error: err.message };
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
// ── App Lifecycle ──
|
|
337
|
+
app.whenReady().then(() => {
|
|
338
|
+
const lock = acquireLock("gui");
|
|
339
|
+
if (!lock.ok) {
|
|
340
|
+
console.log(lock.error);
|
|
341
|
+
app.quit();
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
createService();
|
|
345
|
+
setupIpc();
|
|
346
|
+
createWindow();
|
|
347
|
+
app.on("activate", () => {
|
|
348
|
+
if (BrowserWindow.getAllWindows().length === 0)
|
|
349
|
+
createWindow();
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
app.on("window-all-closed", async () => {
|
|
353
|
+
await service?.dispose();
|
|
354
|
+
service = null;
|
|
355
|
+
releaseLock();
|
|
356
|
+
if (process.platform !== "darwin")
|
|
357
|
+
app.quit();
|
|
358
|
+
});
|
|
359
|
+
app.on("before-quit", async () => {
|
|
360
|
+
await service?.dispose();
|
|
361
|
+
service = null;
|
|
362
|
+
releaseLock();
|
|
363
|
+
});
|
|
364
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { contextBridge, ipcRenderer } from "electron";
|
|
2
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3
|
+
function onIpc(channel, cb) {
|
|
4
|
+
const handler = (_event, ...args) => cb(...args);
|
|
5
|
+
ipcRenderer.on(channel, handler);
|
|
6
|
+
return () => { ipcRenderer.removeListener(channel, handler); };
|
|
7
|
+
}
|
|
8
|
+
contextBridge.exposeInMainWorld("ajan", {
|
|
9
|
+
// ── Agent ──
|
|
10
|
+
sendMessage: (message, model) => ipcRenderer.invoke("agent:send", message, model),
|
|
11
|
+
abort: () => ipcRenderer.invoke("agent:abort"),
|
|
12
|
+
reset: () => ipcRenderer.invoke("agent:reset"),
|
|
13
|
+
getStatus: () => ipcRenderer.invoke("agent:status"),
|
|
14
|
+
// ── Agent Events ──
|
|
15
|
+
onTextChunk: (cb) => onIpc("agent:text-chunk", cb),
|
|
16
|
+
onThinkingChunk: (cb) => onIpc("agent:thinking-chunk", cb),
|
|
17
|
+
onToolCall: (cb) => onIpc("agent:tool-call", cb),
|
|
18
|
+
onToolResult: (cb) => onIpc("agent:tool-result", cb),
|
|
19
|
+
onStepStart: (cb) => onIpc("agent:step-start", cb),
|
|
20
|
+
onComplete: (cb) => onIpc("agent:complete", cb),
|
|
21
|
+
onError: (cb) => onIpc("agent:error", cb),
|
|
22
|
+
onLoadProgress: (cb) => onIpc("agent:load-progress", cb),
|
|
23
|
+
onLoadComplete: (cb) => onIpc("agent:load-complete", cb),
|
|
24
|
+
// ── Config ──
|
|
25
|
+
getConfig: () => ipcRenderer.invoke("config:get"),
|
|
26
|
+
setConfig: (config) => ipcRenderer.invoke("config:set", config),
|
|
27
|
+
// ── Model ──
|
|
28
|
+
getModelInfo: () => ipcRenderer.invoke("model:info"),
|
|
29
|
+
listModels: () => ipcRenderer.invoke("model:list"),
|
|
30
|
+
installModel: (id) => ipcRenderer.invoke("model:install", id),
|
|
31
|
+
removeModel: (id) => ipcRenderer.invoke("model:remove", id),
|
|
32
|
+
useModel: (id) => ipcRenderer.invoke("model:use", id),
|
|
33
|
+
refreshModels: () => ipcRenderer.invoke("model:refresh"),
|
|
34
|
+
// ── Project ──
|
|
35
|
+
listProjects: () => ipcRenderer.invoke("project:list"),
|
|
36
|
+
addProject: (path) => ipcRenderer.invoke("project:add", path),
|
|
37
|
+
removeProject: (path) => ipcRenderer.invoke("project:remove", path),
|
|
38
|
+
setActiveProject: (path) => ipcRenderer.invoke("project:set-active", path),
|
|
39
|
+
pickDir: () => ipcRenderer.invoke("project:pick-dir"),
|
|
40
|
+
projectExists: (path) => ipcRenderer.invoke("project:exists", path),
|
|
41
|
+
// ── App ──
|
|
42
|
+
getVersion: () => ipcRenderer.invoke("app:version"),
|
|
43
|
+
// ── Session ──
|
|
44
|
+
listSessions: () => ipcRenderer.invoke("session:list"),
|
|
45
|
+
loadSession: (id) => ipcRenderer.invoke("session:load", id),
|
|
46
|
+
saveSession: (session) => ipcRenderer.invoke("session:save", session),
|
|
47
|
+
newSession: () => ipcRenderer.invoke("session:new"),
|
|
48
|
+
generateSessionId: () => ipcRenderer.invoke("session:generate-id"),
|
|
49
|
+
getChatHistory: () => ipcRenderer.invoke("session:get-history"),
|
|
50
|
+
onSessionRestore: (cb) => onIpc("agent:session-restore", cb),
|
|
51
|
+
// ── System ──
|
|
52
|
+
getGpuInfo: () => ipcRenderer.invoke("system:gpu"),
|
|
53
|
+
getCwd: () => ipcRenderer.invoke("system:cwd"),
|
|
54
|
+
getNotes: () => ipcRenderer.invoke("system:notes"),
|
|
55
|
+
addNote: (text) => ipcRenderer.invoke("system:notes:add", text),
|
|
56
|
+
// ── GUI Files ──
|
|
57
|
+
readGuiFile: (relativePath) => ipcRenderer.invoke("gui:read-file", relativePath),
|
|
58
|
+
createShortcut: () => ipcRenderer.invoke("gui:create-shortcut"),
|
|
59
|
+
// ── Window Events ──
|
|
60
|
+
onMaximized: (cb) => onIpc("window:maximized-changed", cb),
|
|
61
|
+
isMaximized: () => ipcRenderer.invoke("window:is-maximized"),
|
|
62
|
+
// ── Window Controls ──
|
|
63
|
+
minimize: () => ipcRenderer.invoke("window:minimize"),
|
|
64
|
+
maximize: () => ipcRenderer.invoke("window:maximize"),
|
|
65
|
+
close: () => ipcRenderer.invoke("window:close")
|
|
66
|
+
});
|
|
67
|
+
//# sourceMappingURL=preload.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { LlamaChatSession, type ChatWrapper, type ChatSessionModelFunctions } from "node-llama-cpp";
|
|
2
|
+
import type { AjanConfig, ChatWrapperName } from "../config/types.js";
|
|
3
|
+
export type LlamaEngineOptions = {
|
|
4
|
+
modelPath: string;
|
|
5
|
+
config: AjanConfig;
|
|
6
|
+
chatWrapperName?: ChatWrapperName;
|
|
7
|
+
gpuBackend?: AjanConfig["gpu"];
|
|
8
|
+
onProgress?: (progress: number) => void;
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
};
|
|
11
|
+
export declare class LlamaEngine {
|
|
12
|
+
private llama;
|
|
13
|
+
private model;
|
|
14
|
+
private context;
|
|
15
|
+
private sequence;
|
|
16
|
+
private session;
|
|
17
|
+
private readonly modelPath;
|
|
18
|
+
private readonly config;
|
|
19
|
+
private readonly chatWrapperName?;
|
|
20
|
+
private readonly gpuBackend?;
|
|
21
|
+
private readonly onProgress?;
|
|
22
|
+
private readonly signal?;
|
|
23
|
+
constructor(options: LlamaEngineOptions);
|
|
24
|
+
private get gpuOption();
|
|
25
|
+
static detectSupportedGpus(): Promise<string[]>;
|
|
26
|
+
init(): Promise<void>;
|
|
27
|
+
loadModel(onProgress?: (pct: number) => void): Promise<void>;
|
|
28
|
+
createContext(): Promise<void>;
|
|
29
|
+
createSession(systemPrompt: string): LlamaChatSession;
|
|
30
|
+
/** Konuşma geçmişini temizler ve yeni sistem promptu ile taze bir oturum kurar */
|
|
31
|
+
resetSession(systemPrompt: string): Promise<LlamaChatSession>;
|
|
32
|
+
getSession(): LlamaChatSession | undefined;
|
|
33
|
+
get contextSize(): number;
|
|
34
|
+
getUsedContextTokens(): number;
|
|
35
|
+
get sequenceInstalled(): boolean;
|
|
36
|
+
dispose(): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
export declare function resolveChatWrapper(name: ChatWrapperName): "auto" | ChatWrapper;
|
|
39
|
+
export type { ChatSessionModelFunctions };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { getLlama, getLlamaGpuTypes, LlamaChatSession, Gemma4ChatWrapper, GemmaChatWrapper, QwenChatWrapper, Llama3_1ChatWrapper, MistralChatWrapper, ChatMLChatWrapper, FunctionaryChatWrapper, LlamaLogLevel } from "node-llama-cpp";
|
|
2
|
+
import { logger } from "../utils/paths.js";
|
|
3
|
+
export class LlamaEngine {
|
|
4
|
+
llama;
|
|
5
|
+
model;
|
|
6
|
+
context;
|
|
7
|
+
sequence;
|
|
8
|
+
session;
|
|
9
|
+
modelPath;
|
|
10
|
+
config;
|
|
11
|
+
chatWrapperName;
|
|
12
|
+
gpuBackend;
|
|
13
|
+
onProgress;
|
|
14
|
+
signal;
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.modelPath = options.modelPath;
|
|
17
|
+
this.config = options.config;
|
|
18
|
+
this.chatWrapperName = options.chatWrapperName;
|
|
19
|
+
this.gpuBackend = options.gpuBackend ?? options.config.gpu;
|
|
20
|
+
this.onProgress = options.onProgress;
|
|
21
|
+
this.signal = options.signal;
|
|
22
|
+
}
|
|
23
|
+
get gpuOption() {
|
|
24
|
+
if (!this.gpuBackend)
|
|
25
|
+
return undefined;
|
|
26
|
+
if (this.gpuBackend === "auto")
|
|
27
|
+
return { type: "auto" };
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
static async detectSupportedGpus() {
|
|
31
|
+
try {
|
|
32
|
+
const supported = await getLlamaGpuTypes("supported");
|
|
33
|
+
return supported.filter((g) => typeof g === "string");
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async init() {
|
|
40
|
+
if (this.llama)
|
|
41
|
+
return;
|
|
42
|
+
logger.info(`Motor baslatiliyor... (model: ${this.modelPath})`);
|
|
43
|
+
const supportedGpus = await LlamaEngine.detectSupportedGpus();
|
|
44
|
+
logger.debug(`Desteklenen GPU'lar: ${supportedGpus.join(", ") || "yok (CPU)"}`);
|
|
45
|
+
this.llama = await getLlama({
|
|
46
|
+
gpu: this.gpuOption,
|
|
47
|
+
logLevel: LlamaLogLevel.error,
|
|
48
|
+
progressLogs: "stderr",
|
|
49
|
+
skipDownload: true
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async loadModel(onProgress) {
|
|
53
|
+
await this.init();
|
|
54
|
+
if (this.model)
|
|
55
|
+
return;
|
|
56
|
+
logger.info(`Model yukleniyor: ${this.modelPath}`);
|
|
57
|
+
this.model = await this.llama.loadModel({
|
|
58
|
+
modelPath: this.modelPath,
|
|
59
|
+
gpuLayers: this.config.gpuLayers ?? "auto",
|
|
60
|
+
onLoadProgress: (pct) => {
|
|
61
|
+
const percentage = Math.min(100, Math.max(0, Math.round(pct * 100)));
|
|
62
|
+
onProgress?.(percentage);
|
|
63
|
+
this.onProgress?.(percentage);
|
|
64
|
+
},
|
|
65
|
+
loadSignal: this.signal
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async createContext() {
|
|
69
|
+
await this.loadModel();
|
|
70
|
+
if (this.context)
|
|
71
|
+
return;
|
|
72
|
+
this.context = await this.model.createContext({
|
|
73
|
+
contextSize: this.config.contextSize ?? "auto"
|
|
74
|
+
});
|
|
75
|
+
this.sequence = this.context.getSequence();
|
|
76
|
+
}
|
|
77
|
+
createSession(systemPrompt) {
|
|
78
|
+
if (!this.sequence)
|
|
79
|
+
throw new Error("Oturum olusturmadan once context olusturulmali (createContext)");
|
|
80
|
+
const wrapper = resolveChatWrapper(this.chatWrapperName ?? "auto");
|
|
81
|
+
this.session = new LlamaChatSession({
|
|
82
|
+
contextSequence: this.sequence,
|
|
83
|
+
systemPrompt,
|
|
84
|
+
chatWrapper: wrapper
|
|
85
|
+
});
|
|
86
|
+
return this.session;
|
|
87
|
+
}
|
|
88
|
+
/** Konuşma geçmişini temizler ve yeni sistem promptu ile taze bir oturum kurar */
|
|
89
|
+
async resetSession(systemPrompt) {
|
|
90
|
+
if (!this.sequence)
|
|
91
|
+
throw new Error("Oturum olusturmadan once context olusturulmali (createContext)");
|
|
92
|
+
await this.session?.dispose();
|
|
93
|
+
await this.sequence.clearHistory();
|
|
94
|
+
return this.createSession(systemPrompt);
|
|
95
|
+
}
|
|
96
|
+
getSession() {
|
|
97
|
+
return this.session;
|
|
98
|
+
}
|
|
99
|
+
get contextSize() {
|
|
100
|
+
return this.context?.contextSize ?? 0;
|
|
101
|
+
}
|
|
102
|
+
getUsedContextTokens() {
|
|
103
|
+
return this.sequence?.nextTokenIndex ?? 0;
|
|
104
|
+
}
|
|
105
|
+
get sequenceInstalled() {
|
|
106
|
+
return this.sequence != null;
|
|
107
|
+
}
|
|
108
|
+
async dispose() {
|
|
109
|
+
try {
|
|
110
|
+
this.session?.dispose();
|
|
111
|
+
this.context?.dispose();
|
|
112
|
+
this.model?.dispose();
|
|
113
|
+
this.llama?.dispose();
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
/* dispose hatalari yut */
|
|
117
|
+
}
|
|
118
|
+
this.session = undefined;
|
|
119
|
+
this.context = undefined;
|
|
120
|
+
this.model = undefined;
|
|
121
|
+
this.llama = undefined;
|
|
122
|
+
this.sequence = undefined;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export function resolveChatWrapper(name) {
|
|
126
|
+
switch (name) {
|
|
127
|
+
case "gemma4":
|
|
128
|
+
return new Gemma4ChatWrapper({ reasoning: true });
|
|
129
|
+
case "gemma":
|
|
130
|
+
return new GemmaChatWrapper();
|
|
131
|
+
case "qwen3":
|
|
132
|
+
return new QwenChatWrapper();
|
|
133
|
+
case "llama3":
|
|
134
|
+
return new Llama3_1ChatWrapper();
|
|
135
|
+
case "mistral":
|
|
136
|
+
return new MistralChatWrapper();
|
|
137
|
+
case "chatml":
|
|
138
|
+
return new ChatMLChatWrapper();
|
|
139
|
+
case "functionary":
|
|
140
|
+
return new FunctionaryChatWrapper();
|
|
141
|
+
case "auto":
|
|
142
|
+
default:
|
|
143
|
+
return "auto";
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=llama.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ModelDefinition } from "../config/types.js";
|
|
2
|
+
export type DownloadProgress = {
|
|
3
|
+
totalSize: number;
|
|
4
|
+
downloadedSize: number;
|
|
5
|
+
estimatedTimeLeft: number;
|
|
6
|
+
averageSpeed: number;
|
|
7
|
+
percent?: number;
|
|
8
|
+
};
|
|
9
|
+
export type DownloadOptions = {
|
|
10
|
+
onProgress?: (progress: DownloadProgress) => void;
|
|
11
|
+
showProgress?: boolean;
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
};
|
|
14
|
+
export declare function formatBytes(bytes: number): string;
|
|
15
|
+
export declare function formatTime(ms: number): string;
|
|
16
|
+
export declare function downloadModel(model: ModelDefinition, options?: DownloadOptions): Promise<string>;
|
|
17
|
+
export declare function getModelFileSize(path: string): number;
|
|
18
|
+
export declare function isDownloaded(model: ModelDefinition): boolean;
|