@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.
Files changed (79) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +366 -0
  3. package/config/models.json +54 -0
  4. package/dist/cli/index.d.ts +2 -0
  5. package/dist/cli/index.js +741 -0
  6. package/dist/cli/tui.d.ts +145 -0
  7. package/dist/cli/tui.js +1003 -0
  8. package/dist/config/configManager.d.ts +18 -0
  9. package/dist/config/configManager.js +204 -0
  10. package/dist/config/types.d.ts +98 -0
  11. package/dist/config/types.js +2 -0
  12. package/dist/core/agent.d.ts +88 -0
  13. package/dist/core/agent.js +222 -0
  14. package/dist/electron/main.d.ts +1 -0
  15. package/dist/electron/main.js +364 -0
  16. package/dist/electron/preload.d.ts +1 -0
  17. package/dist/electron/preload.js +67 -0
  18. package/dist/engine/llama.d.ts +39 -0
  19. package/dist/engine/llama.js +146 -0
  20. package/dist/engine/modelManager.d.ts +18 -0
  21. package/dist/engine/modelManager.js +73 -0
  22. package/dist/index.d.ts +9 -0
  23. package/dist/index.js +9 -0
  24. package/dist/service.d.ts +90 -0
  25. package/dist/service.js +268 -0
  26. package/dist/tools/data.d.ts +3 -0
  27. package/dist/tools/data.js +111 -0
  28. package/dist/tools/edit.d.ts +2 -0
  29. package/dist/tools/edit.js +48 -0
  30. package/dist/tools/file.d.ts +6 -0
  31. package/dist/tools/file.js +274 -0
  32. package/dist/tools/filesys.d.ts +3 -0
  33. package/dist/tools/filesys.js +74 -0
  34. package/dist/tools/memory.d.ts +3 -0
  35. package/dist/tools/memory.js +69 -0
  36. package/dist/tools/patch.d.ts +15 -0
  37. package/dist/tools/patch.js +129 -0
  38. package/dist/tools/registry.d.ts +10 -0
  39. package/dist/tools/registry.js +64 -0
  40. package/dist/tools/taskComplete.d.ts +2 -0
  41. package/dist/tools/taskComplete.js +19 -0
  42. package/dist/tools/terminal.d.ts +2 -0
  43. package/dist/tools/terminal.js +130 -0
  44. package/dist/tools/utils.d.ts +7 -0
  45. package/dist/tools/utils.js +63 -0
  46. package/dist/tools/web.d.ts +8 -0
  47. package/dist/tools/web.js +181 -0
  48. package/dist/utils/clipboard.d.ts +2 -0
  49. package/dist/utils/clipboard.js +40 -0
  50. package/dist/utils/lock.d.ts +12 -0
  51. package/dist/utils/lock.js +68 -0
  52. package/dist/utils/paths.d.ts +13 -0
  53. package/dist/utils/paths.js +58 -0
  54. package/dist/utils/projectContext.d.ts +2 -0
  55. package/dist/utils/projectContext.js +24 -0
  56. package/dist/utils/sacGuard.d.ts +4 -0
  57. package/dist/utils/sacGuard.js +47 -0
  58. package/dist/utils/sessions.d.ts +29 -0
  59. package/dist/utils/sessions.js +64 -0
  60. package/dist/utils/spinner.d.ts +10 -0
  61. package/dist/utils/spinner.js +32 -0
  62. package/package.json +106 -0
  63. package/src/gui/assets/image/AJAN_LOGO.svg +1 -0
  64. package/src/gui/assets/image/ajan.png +0 -0
  65. package/src/gui/assets/image/logo.svg +1 -0
  66. package/src/gui/assets/image/opencode.svg +18 -0
  67. package/src/gui/css/style.css +430 -0
  68. package/src/gui/html/chat.html +34 -0
  69. package/src/gui/html/downloads.html +11 -0
  70. package/src/gui/html/hakkinda.html +26 -0
  71. package/src/gui/html/index.html +82 -0
  72. package/src/gui/html/projects.html +12 -0
  73. package/src/gui/html/settings.html +4 -0
  74. package/src/gui/js/app.js +197 -0
  75. package/src/gui/js/chat.js +434 -0
  76. package/src/gui/js/downloads.js +172 -0
  77. package/src/gui/js/hakkinda.js +16 -0
  78. package/src/gui/js/projects.js +113 -0
  79. package/src/gui/js/settings.js +177 -0
@@ -0,0 +1,197 @@
1
+ "use strict";
2
+ /// <reference path="./ajan.d.ts" />
3
+ const mainContent = document.getElementById("main-content");
4
+ const sidebarLinks = document.querySelectorAll(".sidebar-link[data-page]");
5
+ const headerNavBtns = document.querySelectorAll(".header-nav-btn");
6
+ const pageTitle = document.getElementById("page-title");
7
+ let currentPage = "chat";
8
+ let currentCleanup = null;
9
+ // ── Toast ──
10
+ let toastTimer = null;
11
+ function showToast(msg, type = "success") {
12
+ let toast = document.getElementById("global-toast");
13
+ if (!toast) {
14
+ toast = document.createElement("div");
15
+ toast.id = "global-toast";
16
+ toast.className = "toast";
17
+ document.body.appendChild(toast);
18
+ }
19
+ toast.textContent = msg;
20
+ toast.className = `toast ${type}`;
21
+ requestAnimationFrame(() => toast.classList.add("show"));
22
+ if (toastTimer)
23
+ clearTimeout(toastTimer);
24
+ toastTimer = setTimeout(() => toast.classList.remove("show"), 3000);
25
+ }
26
+ window.ajanToast = showToast;
27
+ // ── Maximize Durumu Dinleyici ──
28
+ if (window.ajan?.onMaximized) {
29
+ window.ajan.onMaximized((isMax) => {
30
+ document.body.classList.toggle("is-maximized", isMax);
31
+ });
32
+ window.ajan.isMaximized?.().then((isMax) => {
33
+ if (isMax)
34
+ document.body.classList.add("is-maximized");
35
+ }).catch(() => { });
36
+ }
37
+ // ── Dosya Okuyucu (yalnızca HTML sayfa şablonları için) ──
38
+ const fileCache = new Map();
39
+ async function readGuiFile(relativePath) {
40
+ if (fileCache.has(relativePath))
41
+ return fileCache.get(relativePath);
42
+ const result = await window.ajan.readGuiFile(relativePath);
43
+ if (result.ok && result.content) {
44
+ fileCache.set(relativePath, result.content);
45
+ return result.content;
46
+ }
47
+ throw new Error(result.error || "Dosya okunamadı");
48
+ }
49
+ // ── Active State Senkronizasyonu ──
50
+ function setActivePage(page) {
51
+ sidebarLinks.forEach(l => l.classList.toggle("active", l.dataset.page === page));
52
+ headerNavBtns.forEach(b => b.classList.toggle("active", b.dataset.page === page));
53
+ }
54
+ // ── Sidebar Oturum Listesi ──
55
+ let activeSessionId = null;
56
+ function renderSidebarSessions() {
57
+ const listEl = document.getElementById("sidebar-sessions");
58
+ if (!listEl)
59
+ return;
60
+ if (!window.ajan?.listSessions)
61
+ return;
62
+ window.ajan.listSessions().then((sessions) => {
63
+ console.log("[sidebarSessions] count=", sessions ? sessions.length : 0, "active=", activeSessionId);
64
+ if (!sessions || sessions.length === 0) {
65
+ listEl.innerHTML = '<div class="sidebar-sessions-empty">Oturum yok</div>';
66
+ return;
67
+ }
68
+ listEl.innerHTML = "";
69
+ for (const s of sessions) {
70
+ const item = document.createElement("div");
71
+ item.className = "sidebar-session-item" + (s.id === activeSessionId ? " active" : "");
72
+ item.dataset.sessionId = s.id;
73
+ const title = document.createElement("span");
74
+ title.className = "sidebar-session-title-text";
75
+ title.textContent = s.title || "Yeni Sohbet";
76
+ item.appendChild(title);
77
+ item.addEventListener("click", () => openSession(s.id));
78
+ listEl.appendChild(item);
79
+ }
80
+ }).catch(() => { });
81
+ }
82
+ async function openSession(id) {
83
+ console.log("[openSession] start", id, "currentPage=", currentPage);
84
+ const result = await window.ajan.loadSession(id);
85
+ console.log("[openSession] loadSession ok=", result.ok, "data=", result.data ? result.data.id : null);
86
+ if (!result.ok || !result.data) {
87
+ showToast(`Oturum açılamadı: ${result.error || "Bilinmeyen hata"}`, "error");
88
+ return;
89
+ }
90
+ activeSessionId = id;
91
+ renderSidebarSessions();
92
+ await loadPage("chat", true);
93
+ console.log("[openSession] dispatch restore");
94
+ document.dispatchEvent(new CustomEvent("ajan:restore-session", {
95
+ detail: result.data
96
+ }));
97
+ }
98
+ window.addEventListener("ajan:session-restored", () => {
99
+ renderSidebarSessions();
100
+ });
101
+ window.addEventListener("ajan:session-active", (e) => {
102
+ activeSessionId = (e.detail) ?? null;
103
+ renderSidebarSessions();
104
+ });
105
+ // ── Page Loader ──
106
+ let currentLoad = null;
107
+ const pageTitles = {
108
+ chat: "AJAN - Sohbet",
109
+ downloads: "AJAN - İndirmeler",
110
+ settings: "AJAN - Ayarlar",
111
+ projects: "AJAN - Projeler",
112
+ hakkinda: "AJAN - Hakkında"
113
+ };
114
+ function loadPage(page, force = false) {
115
+ if (!force && page === currentPage && mainContent.children.length > 0 && !currentLoad)
116
+ return Promise.resolve();
117
+ if (!force && currentLoad)
118
+ return currentLoad;
119
+ currentLoad = (async () => {
120
+ if (currentCleanup) {
121
+ currentCleanup();
122
+ currentCleanup = null;
123
+ }
124
+ mainContent.innerHTML = '<div class="empty-state"><div class="spinner"></div><div class="empty-state-text">Yükleniyor...</div></div>';
125
+ try {
126
+ const html = await readGuiFile(`html/${page}.html`);
127
+ mainContent.innerHTML = html;
128
+ pageTitle.textContent = pageTitles[page] || "AJAN AI";
129
+ setActivePage(page);
130
+ currentPage = page;
131
+ // Script'ler henüz yüklenmemiş olabilir (renderSidebarSessions+readGuiFile IPC'si
132
+ // chat.js gibi page modül script'lerinden daha önce çözülebilir). Init hazır olana dek bekle.
133
+ let init = window.AjanInit?.[page];
134
+ const deadline = Date.now() + 5000;
135
+ while (typeof init !== "function" && Date.now() < deadline) {
136
+ await new Promise(r => setTimeout(r, 25));
137
+ init = window.AjanInit?.[page];
138
+ }
139
+ if (typeof init === "function") {
140
+ currentCleanup = init(mainContent) || null;
141
+ }
142
+ }
143
+ catch (err) {
144
+ mainContent.innerHTML = `<div class="empty-state"><div class="empty-state-icon">⚠️</div><div class="empty-state-text">${err.message}</div></div>`;
145
+ }
146
+ finally {
147
+ currentLoad = null;
148
+ }
149
+ })();
150
+ return currentLoad;
151
+ }
152
+ // ── Sidebar Navigation ──
153
+ sidebarLinks.forEach(link => {
154
+ link.addEventListener("click", (e) => {
155
+ e.preventDefault();
156
+ const page = link.dataset.page;
157
+ if (page && page !== currentPage)
158
+ loadPage(page);
159
+ });
160
+ });
161
+ // ── Header Nav Icons ──
162
+ headerNavBtns.forEach(btn => {
163
+ btn.addEventListener("click", () => {
164
+ const page = btn.dataset.page;
165
+ if (page && page !== currentPage)
166
+ loadPage(page);
167
+ });
168
+ });
169
+ // ── Yeni Sohbet (Header) ──
170
+ document.getElementById("header-new-chat")?.addEventListener("click", () => {
171
+ document.dispatchEvent(new CustomEvent("ajan:new-chat"));
172
+ });
173
+ // ── Yeni Proje ──
174
+ document.querySelector("[data-action='new-project']")?.addEventListener("click", (e) => {
175
+ e.preventDefault();
176
+ loadPage("projects").then(() => {
177
+ document.querySelector("#btn-add-project")?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
178
+ });
179
+ });
180
+ // ── Versiyon Rozeti ──
181
+ if (window.ajan?.getVersion) {
182
+ window.ajan.getVersion().then((v) => {
183
+ const el = document.getElementById("version-info");
184
+ if (el)
185
+ el.textContent = `v${v}`;
186
+ }).catch(() => { });
187
+ }
188
+ // ── Window Controls ──
189
+ document.getElementById("btn-minimize")?.addEventListener("click", () => window.ajan?.minimize?.());
190
+ document.getElementById("btn-maximize")?.addEventListener("click", () => window.ajan?.maximize?.());
191
+ document.getElementById("btn-close")?.addEventListener("click", () => window.ajan?.close?.());
192
+ // AjanInit globalini burada başlat
193
+ if (!window.AjanInit)
194
+ window.AjanInit = {};
195
+ // ── Init ──
196
+ renderSidebarSessions();
197
+ loadPage("chat");
@@ -0,0 +1,434 @@
1
+ "use strict";
2
+ /// <reference path="./ajan.d.ts" />
3
+ (function () {
4
+ "use strict";
5
+ if (!window.AjanInit)
6
+ window.AjanInit = {};
7
+ window.AjanInit.chat = function (container) {
8
+ console.log("[chatInit] registered, has message area=", !!container.querySelector("#chat-logs"));
9
+ const ajan = window.ajan;
10
+ const chatLogs = container.querySelector("#chat-logs");
11
+ const chatInput = container.querySelector("#chat-input");
12
+ const chatSendBtn = container.querySelector("#chat-send-btn");
13
+ const chatStopBtn = container.querySelector("#chat-stop-btn");
14
+ const modelSelect = container.querySelector("#model-select");
15
+ const stepBar = container.querySelector("#step-bar");
16
+ const stepFill = container.querySelector("#step-fill");
17
+ const stepText = container.querySelector("#step-text");
18
+ const sessionTitle = container.querySelector("#chat-session-title");
19
+ const chatLogo = container.querySelector("#chat-logo");
20
+ const chatContainer = container.querySelector(".chat-container");
21
+ const loadBar = container.querySelector("#load-progress-bar");
22
+ const loadFill = container.querySelector("#load-progress-fill");
23
+ const loadText = container.querySelector("#load-progress-text");
24
+ let isActive = false;
25
+ let currentAssistantEl = null;
26
+ let currentThinkingEl = null;
27
+ let unsubscribeFns = [];
28
+ let currentSessionId = null;
29
+ let sessionMessages = [];
30
+ let defaultModelId = null;
31
+ // ── Helpers ──
32
+ function scrollToBottom() {
33
+ requestAnimationFrame(() => {
34
+ if (chatLogs)
35
+ chatLogs.scrollTop = chatLogs.scrollHeight;
36
+ });
37
+ }
38
+ function escapeHTML(str) {
39
+ const map = { "&": "&amp;", "<": "&lt;", ">": "&gt;", "'": "&#39;", '"': "&quot;" };
40
+ return str.replace(/[&<>'"]/g, c => map[c] || c);
41
+ }
42
+ function setActive(active) {
43
+ isActive = active;
44
+ if (chatSendBtn)
45
+ chatSendBtn.disabled = active;
46
+ if (chatInput)
47
+ chatInput.disabled = active;
48
+ if (chatStopBtn)
49
+ chatStopBtn.style.display = active ? "flex" : "none";
50
+ if (chatSendBtn)
51
+ chatSendBtn.style.display = active ? "none" : "flex";
52
+ if (!active && chatInput)
53
+ chatInput.focus();
54
+ }
55
+ function updateLogoVisibility() {
56
+ if (chatLogo) {
57
+ chatLogo.style.display = sessionMessages.length > 0 ? "none" : "block";
58
+ }
59
+ if (chatContainer) {
60
+ chatContainer.classList.toggle("chat-empty", sessionMessages.length === 0);
61
+ }
62
+ }
63
+ // ── Messages ──
64
+ function appendMessage(sender, text, extraClass) {
65
+ if (!chatLogs)
66
+ return null;
67
+ const wrapper = document.createElement("div");
68
+ wrapper.className = `msg msg-${sender}${extraClass ? " " + extraClass : ""}`;
69
+ const label = sender === "user" ? "SİZ" : sender === "ai" ? "AJAN" : "";
70
+ wrapper.innerHTML = `<div class="msg-bubble">${label ? `<span class="msg-label">${label}</span>` : ""}<span>${escapeHTML(text)}</span></div>`;
71
+ chatLogs.appendChild(wrapper);
72
+ scrollToBottom();
73
+ return wrapper;
74
+ }
75
+ function appendRaw(sender, html, extraClass) {
76
+ if (!chatLogs)
77
+ return null;
78
+ const wrapper = document.createElement("div");
79
+ wrapper.className = `msg msg-${sender}${extraClass ? " " + extraClass : ""}`;
80
+ wrapper.innerHTML = `<div class="msg-bubble">${html}</div>`;
81
+ chatLogs.appendChild(wrapper);
82
+ scrollToBottom();
83
+ return wrapper;
84
+ }
85
+ function showTyping() {
86
+ if (!chatLogs)
87
+ return null;
88
+ const el = document.createElement("div");
89
+ el.className = "typing-indicator";
90
+ el.innerHTML = '<div class="typing-bubble"><div class="spinner"></div><span>Analiz ediliyor...</span></div>';
91
+ chatLogs.appendChild(el);
92
+ scrollToBottom();
93
+ return el;
94
+ }
95
+ function removeTyping(el) {
96
+ if (el && el.parentNode)
97
+ el.remove();
98
+ }
99
+ // ── Assistant streaming ──
100
+ function startAssistantBlock() {
101
+ if (!chatLogs)
102
+ return;
103
+ const wrapper = document.createElement("div");
104
+ wrapper.className = "msg msg-ai";
105
+ wrapper.innerHTML = `<div class="msg-bubble"><span class="msg-label">AJAN</span><span class="assistant-text"></span></div>`;
106
+ chatLogs.appendChild(wrapper);
107
+ currentAssistantEl = wrapper.querySelector(".assistant-text");
108
+ scrollToBottom();
109
+ }
110
+ function appendToAssistant(text) {
111
+ if (!currentAssistantEl)
112
+ startAssistantBlock();
113
+ if (currentAssistantEl)
114
+ currentAssistantEl.textContent += text;
115
+ scrollToBottom();
116
+ }
117
+ function finalizeAssistant() {
118
+ if (currentAssistantEl) {
119
+ sessionMessages.push({ kind: "assistant", text: currentAssistantEl.textContent || "" });
120
+ }
121
+ currentAssistantEl = null;
122
+ }
123
+ // ── Thinking ──
124
+ function showThinking() {
125
+ if (currentThinkingEl)
126
+ return;
127
+ const el = appendRaw("ai", '<span class="msg-label" style="color:#888">Düşünce...</span><span class="think-text" style="font-style:italic;color:#888"></span>', "msg-think");
128
+ if (el)
129
+ currentThinkingEl = el.querySelector(".think-text");
130
+ }
131
+ function appendThinking(text) {
132
+ if (!currentThinkingEl)
133
+ showThinking();
134
+ if (currentThinkingEl)
135
+ currentThinkingEl.textContent += text;
136
+ scrollToBottom();
137
+ }
138
+ function hideThinking() {
139
+ if (currentThinkingEl) {
140
+ sessionMessages.push({ kind: "thought", text: currentThinkingEl.textContent || "" });
141
+ }
142
+ currentThinkingEl = null;
143
+ }
144
+ // ── Tool events ──
145
+ function showToolCall(name) {
146
+ appendRaw("ai", `<div class="tool-status">⚙️ ${escapeHTML(name)} çalıştırılıyor...</div>`, "msg-tool");
147
+ sessionMessages.push({ kind: "tool", name, state: "running", startedAt: Date.now() });
148
+ }
149
+ function showToolResult(name, ok, output) {
150
+ const truncated = output.length > 500 ? output.slice(0, 500) + "\n... (kesildi)" : output;
151
+ if (chatLogs) {
152
+ const toolEls = chatLogs.querySelectorAll(".msg-tool");
153
+ const lastToolEl = toolEls[toolEls.length - 1];
154
+ if (lastToolEl) {
155
+ const statusClass = ok ? "ok" : "err";
156
+ const icon = ok ? "✅" : "❌";
157
+ const bubble = lastToolEl.querySelector(".msg-bubble");
158
+ if (bubble) {
159
+ bubble.innerHTML = `
160
+ <div class="tool-status ${statusClass}">${icon} ${escapeHTML(name)}</div>
161
+ <div class="tool-output">${escapeHTML(truncated)}</div>
162
+ `;
163
+ }
164
+ }
165
+ }
166
+ for (let i = sessionMessages.length - 1; i >= 0; i--) {
167
+ const m = sessionMessages[i];
168
+ if (m && m.kind === "tool" && m.name === name && m.state === "running") {
169
+ m.state = ok ? "done" : "error";
170
+ m.elapsed = Date.now() - (m.startedAt || Date.now());
171
+ m.output = truncated;
172
+ break;
173
+ }
174
+ }
175
+ }
176
+ // ── Step bar ──
177
+ function updateStep(step, max) {
178
+ if (stepBar) {
179
+ stepBar.style.display = "flex";
180
+ if (stepFill)
181
+ stepFill.style.width = `${(step / max) * 100}%`;
182
+ if (stepText)
183
+ stepText.textContent = `Adım ${step}/${max}`;
184
+ }
185
+ }
186
+ function hideStep() {
187
+ if (stepBar)
188
+ stepBar.style.display = "none";
189
+ }
190
+ // ── Load progress ──
191
+ function showLoadProgress(pct) {
192
+ if (!loadBar)
193
+ return;
194
+ loadBar.style.display = "flex";
195
+ if (loadFill)
196
+ loadFill.style.width = `${pct}%`;
197
+ if (loadText)
198
+ loadText.textContent = `Model yükleniyor... %${pct}`;
199
+ }
200
+ function hideLoadProgress() {
201
+ if (loadBar)
202
+ loadBar.style.display = "none";
203
+ }
204
+ // ── Session Management ──
205
+ function renderStoredMessages(messages) {
206
+ if (chatLogs)
207
+ chatLogs.innerHTML = "";
208
+ for (const msg of messages) {
209
+ switch (msg.kind) {
210
+ case "user":
211
+ appendMessage("user", msg.text || "");
212
+ break;
213
+ case "assistant":
214
+ appendMessage("ai", msg.text || "");
215
+ break;
216
+ case "thought":
217
+ appendRaw("ai", `<span class="msg-label" style="color:#888">Düşünce</span><span style="font-style:italic;color:#888">${escapeHTML(msg.text || "")}</span>`, "msg-think");
218
+ break;
219
+ case "tool": {
220
+ const statusClass = msg.state === "done" ? "ok" : msg.state === "error" ? "err" : "";
221
+ const icon = msg.state === "done" ? "✅" : msg.state === "error" ? "❌" : "⚙️";
222
+ const output = msg.output ? (msg.output.length > 500 ? msg.output.slice(0, 500) + "\n... (kesildi)" : msg.output) : "";
223
+ appendRaw("ai", `<div class="tool-status ${statusClass}">${icon} ${escapeHTML(msg.name || "")}</div>${output ? `<div class="tool-output">${escapeHTML(output)}</div>` : ""}`, "msg-tool");
224
+ break;
225
+ }
226
+ case "info":
227
+ appendRaw("ai", `<span style="color:#666;font-size:0.85rem">${escapeHTML(msg.text || "")}</span>`, "msg-info");
228
+ break;
229
+ }
230
+ }
231
+ updateLogoVisibility();
232
+ }
233
+ async function saveCurrentSession() {
234
+ const meaningful = sessionMessages.filter(m => m.kind !== "info");
235
+ if (meaningful.length === 0)
236
+ return;
237
+ const now = new Date().toISOString();
238
+ if (!currentSessionId) {
239
+ const generated = ajan.generateSessionId ? await ajan.generateSessionId().catch(() => "") : "";
240
+ currentSessionId = generated || now.replace(/[^0-9]/g, "").slice(0, 14);
241
+ }
242
+ let history = [];
243
+ if (ajan.getChatHistory) {
244
+ try {
245
+ history = await ajan.getChatHistory();
246
+ }
247
+ catch { /* empty */ }
248
+ }
249
+ const session = {
250
+ id: currentSessionId,
251
+ title: sessionTitle?.textContent || "Yeni Sohbet",
252
+ mode: "agent",
253
+ createdAt: now,
254
+ updatedAt: now,
255
+ history: history,
256
+ messages: meaningful
257
+ };
258
+ try {
259
+ if (ajan.saveSession)
260
+ await ajan.saveSession(session);
261
+ }
262
+ catch { /* empty */ }
263
+ document.dispatchEvent(new CustomEvent("ajan:session-active", { detail: currentSessionId }));
264
+ document.dispatchEvent(new Event("ajan:session-restored"));
265
+ }
266
+ function restoreSession(data) {
267
+ if (!data || !data.messages)
268
+ return;
269
+ currentSessionId = data.id;
270
+ sessionMessages = data.messages || [];
271
+ if (sessionTitle && data.title)
272
+ sessionTitle.textContent = data.title;
273
+ renderStoredMessages(sessionMessages);
274
+ document.dispatchEvent(new CustomEvent("ajan:session-active", { detail: data.id }));
275
+ }
276
+ const unsubRestore = ajan.onSessionRestore ? ajan.onSessionRestore((data) => {
277
+ restoreSession(data);
278
+ document.dispatchEvent(new Event("ajan:session-restored"));
279
+ }) : null;
280
+ const onDocRestore = (e) => {
281
+ const data = e.detail;
282
+ console.log("[chat] restore-session received, id=", data ? data.id : null, "messages=", data ? (data.messages || []).length : 0);
283
+ if (data)
284
+ restoreSession(data);
285
+ };
286
+ document.addEventListener("ajan:restore-session", onDocRestore);
287
+ // ── Send Message ──
288
+ async function sendMessage() {
289
+ if (!chatInput)
290
+ return;
291
+ const text = chatInput.value.trim();
292
+ if (!text || isActive)
293
+ return;
294
+ sessionMessages.push({ kind: "user", text });
295
+ appendMessage("user", text);
296
+ chatInput.value = "";
297
+ setActive(true);
298
+ updateLogoVisibility();
299
+ if (sessionMessages.filter(m => m.kind === "user").length === 1 && sessionTitle) {
300
+ const title = text.length > 30 ? text.slice(0, 30) + "..." : text;
301
+ sessionTitle.textContent = title;
302
+ }
303
+ const typing = showTyping();
304
+ startAssistantBlock();
305
+ const unsubs = [];
306
+ if (ajan.onTextChunk)
307
+ unsubs.push(ajan.onTextChunk((t) => { removeTyping(typing); appendToAssistant(t); }));
308
+ if (ajan.onThinkingChunk)
309
+ unsubs.push(ajan.onThinkingChunk((t) => { removeTyping(typing); showThinking(); appendThinking(t); }));
310
+ if (ajan.onToolCall)
311
+ unsubs.push(ajan.onToolCall((name) => { hideThinking(); showToolCall(name); }));
312
+ if (ajan.onToolResult)
313
+ unsubs.push(ajan.onToolResult((name, ok, out) => { showToolResult(name, ok, out); }));
314
+ if (ajan.onStepStart)
315
+ unsubs.push(ajan.onStepStart((s, m) => updateStep(s, m)));
316
+ if (ajan.onLoadProgress)
317
+ unsubs.push(ajan.onLoadProgress((pct) => showLoadProgress(pct)));
318
+ if (ajan.onLoadComplete)
319
+ unsubs.push(ajan.onLoadComplete(() => hideLoadProgress()));
320
+ if (ajan.onComplete)
321
+ unsubs.push(ajan.onComplete((response) => {
322
+ hideLoadProgress();
323
+ if (currentAssistantEl && !currentAssistantEl.textContent) {
324
+ currentAssistantEl.textContent = response;
325
+ }
326
+ }));
327
+ if (ajan.onError)
328
+ unsubs.push(ajan.onError((message) => {
329
+ hideLoadProgress();
330
+ removeTyping(typing);
331
+ if (!currentAssistantEl?.textContent)
332
+ appendMessage("ai", `⚠️ ${message}`);
333
+ }));
334
+ unsubscribeFns = unsubs;
335
+ try {
336
+ const result = await ajan.sendMessage(text, (modelSelect?.value || defaultModelId || "gemma-4-e2b-q4-k-m"));
337
+ removeTyping(typing);
338
+ finalizeAssistant();
339
+ hideThinking();
340
+ hideStep();
341
+ hideLoadProgress();
342
+ if (!result.ok && result.error) {
343
+ appendMessage("ai", `⚠️ ${result.error}`);
344
+ }
345
+ await saveCurrentSession();
346
+ }
347
+ catch (err) {
348
+ removeTyping(typing);
349
+ finalizeAssistant();
350
+ appendMessage("ai", `⚠️ Hata: ${err.message}`);
351
+ }
352
+ finally {
353
+ unsubscribeFns.forEach(fn => fn?.());
354
+ unsubscribeFns = [];
355
+ setActive(false);
356
+ }
357
+ }
358
+ // ── Yeni Sohbet ──
359
+ async function newChat() {
360
+ if (isActive)
361
+ return;
362
+ currentSessionId = null;
363
+ sessionMessages = [];
364
+ if (chatLogs)
365
+ chatLogs.innerHTML = "";
366
+ if (sessionTitle)
367
+ sessionTitle.textContent = "Yeni Sohbet";
368
+ updateLogoVisibility();
369
+ try {
370
+ if (ajan.newSession)
371
+ await ajan.newSession();
372
+ }
373
+ catch { /* empty */ }
374
+ if (chatInput)
375
+ chatInput.focus();
376
+ }
377
+ // ── Event Bindings ──
378
+ chatSendBtn?.addEventListener("click", sendMessage);
379
+ chatStopBtn?.addEventListener("click", () => {
380
+ try {
381
+ if (ajan.abort)
382
+ ajan.abort();
383
+ }
384
+ catch { /* empty */ }
385
+ });
386
+ chatInput?.addEventListener("keydown", (e) => {
387
+ if (e.key === "Enter" && !e.shiftKey) {
388
+ e.preventDefault();
389
+ sendMessage();
390
+ }
391
+ });
392
+ const onNewChat = () => { newChat(); };
393
+ document.addEventListener("ajan:new-chat", onNewChat);
394
+ // ── Load Model Info ──
395
+ async function loadModelInfo() {
396
+ if (!ajan?.listModels)
397
+ return;
398
+ try {
399
+ const configPromise = ajan.getConfig ? ajan.getConfig().catch(() => null) : Promise.resolve(null);
400
+ const [models, config] = await Promise.all([
401
+ ajan.listModels(),
402
+ configPromise
403
+ ]);
404
+ if (config && typeof config === "object" && config.modelId)
405
+ defaultModelId = config.modelId;
406
+ if (!models || !modelSelect)
407
+ return;
408
+ modelSelect.innerHTML = "";
409
+ for (const m of models) {
410
+ const opt = document.createElement("option");
411
+ opt.value = m.model.id;
412
+ opt.textContent = `${m.model.name}${m.active || m.model.id === defaultModelId ? " ★" : ""}`;
413
+ if (m.active || m.model.id === defaultModelId)
414
+ opt.selected = true;
415
+ modelSelect.appendChild(opt);
416
+ }
417
+ }
418
+ catch { /* empty */ }
419
+ }
420
+ loadModelInfo();
421
+ // ── Focus ──
422
+ setTimeout(() => chatInput?.focus(), 100);
423
+ updateLogoVisibility();
424
+ // ── Cleanup ──
425
+ return function cleanup() {
426
+ unsubscribeFns.forEach(fn => fn?.());
427
+ unsubscribeFns = [];
428
+ if (unsubRestore)
429
+ unsubRestore();
430
+ document.removeEventListener("ajan:new-chat", onNewChat);
431
+ document.removeEventListener("ajan:restore-session", onDocRestore);
432
+ };
433
+ };
434
+ })();