antri_cli 1.26.1 → 1.28.0
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/README.md +89 -42
- package/assets/desktop_panel.png +0 -0
- package/assets/home.png +0 -0
- package/dist/cli/promptToolkit.d.ts.map +1 -1
- package/dist/cli/promptToolkit.js +2 -0
- package/dist/cli/promptToolkit.js.map +1 -1
- package/dist/cli/shortcuts.d.ts.map +1 -1
- package/dist/cli/shortcuts.js +13 -0
- package/dist/cli/shortcuts.js.map +1 -1
- package/dist/core/config.js +1 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/updater.d.ts +1 -1
- package/dist/core/updater.js +1 -1
- package/dist/desktop/public/app.js +699 -0
- package/dist/desktop/public/index.html +338 -0
- package/dist/desktop/public/style.css +962 -0
- package/dist/desktop/server.d.ts +11 -0
- package/dist/desktop/server.d.ts.map +1 -0
- package/dist/desktop/server.js +361 -0
- package/dist/desktop/server.js.map +1 -0
- package/dist/index.js +30 -2
- package/dist/index.js.map +1 -1
- package/dist/memory/episodic.d.ts +1 -0
- package/dist/memory/episodic.d.ts.map +1 -1
- package/dist/memory/episodic.js +3 -0
- package/dist/memory/episodic.js.map +1 -1
- package/dist/memory/manager.d.ts +6 -0
- package/dist/memory/manager.d.ts.map +1 -1
- package/dist/memory/manager.js +8 -0
- package/dist/memory/manager.js.map +1 -1
- package/dist/mobile/public/app.js +449 -0
- package/dist/mobile/public/index.html +287 -0
- package/dist/mobile/public/manifest.json +16 -0
- package/dist/mobile/public/style.css +589 -0
- package/dist/mobile/server.d.ts +14 -0
- package/dist/mobile/server.d.ts.map +1 -0
- package/dist/mobile/server.js +232 -0
- package/dist/mobile/server.js.map +1 -0
- package/package.json +5 -2
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
// ANTRI Mobile Standalone Client Engine
|
|
2
|
+
|
|
3
|
+
let mobileConfig = {
|
|
4
|
+
provider: 'deepseek',
|
|
5
|
+
model: 'deepseek-chat',
|
|
6
|
+
apiKey: '',
|
|
7
|
+
baseUrl: '',
|
|
8
|
+
mode: 'vibe',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
let mobileProfiles = {};
|
|
12
|
+
let activeProfileName = 'mobile_profile_1';
|
|
13
|
+
let attachedMobileFiles = [];
|
|
14
|
+
let mobileEpisodes = [];
|
|
15
|
+
let mobileSemanticItems = [];
|
|
16
|
+
|
|
17
|
+
// Initialize on Load
|
|
18
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
19
|
+
loadMobileSettings();
|
|
20
|
+
loadMobileProfiles();
|
|
21
|
+
loadMobileMemory();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Load Settings from LocalStorage
|
|
25
|
+
function loadMobileSettings() {
|
|
26
|
+
const saved = localStorage.getItem('antri_mobile_settings');
|
|
27
|
+
if (saved) {
|
|
28
|
+
try {
|
|
29
|
+
mobileConfig = { ...mobileConfig, ...JSON.parse(saved) };
|
|
30
|
+
} catch (e) {}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
document.getElementById('mobile-provider-select').value = mobileConfig.provider;
|
|
34
|
+
document.getElementById('mobile-model-input').value = mobileConfig.model;
|
|
35
|
+
document.getElementById('mobile-api-key-input').value = mobileConfig.apiKey || '';
|
|
36
|
+
document.getElementById('mobile-base-url-input').value = mobileConfig.baseUrl || '';
|
|
37
|
+
|
|
38
|
+
switchMode(mobileConfig.mode || 'vibe', false);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function saveMobileSettings() {
|
|
42
|
+
mobileConfig.provider = document.getElementById('mobile-provider-select').value;
|
|
43
|
+
mobileConfig.model = document.getElementById('mobile-model-input').value.trim() || 'deepseek-chat';
|
|
44
|
+
mobileConfig.apiKey = document.getElementById('mobile-api-key-input').value.trim();
|
|
45
|
+
mobileConfig.baseUrl = document.getElementById('mobile-base-url-input').value.trim();
|
|
46
|
+
|
|
47
|
+
localStorage.setItem('antri_mobile_settings', JSON.stringify(mobileConfig));
|
|
48
|
+
alert('Settings saved to mobile device.');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function onMobileProviderChange(prov) {
|
|
52
|
+
mobileConfig.provider = prov;
|
|
53
|
+
const modelMap = {
|
|
54
|
+
deepseek: 'deepseek-chat',
|
|
55
|
+
openai: 'gpt-4o',
|
|
56
|
+
anthropic: 'claude-3-7-sonnet',
|
|
57
|
+
gemini: 'gemini-2.5-flash',
|
|
58
|
+
cerebras: 'llama-3.3-70b',
|
|
59
|
+
cohere: 'command-r-plus',
|
|
60
|
+
vortex: 'vortex-llama-3.3-70b-instruct',
|
|
61
|
+
opencode: 'opencode/deepseek-coder-v2.5',
|
|
62
|
+
'nvidia-nim': 'meta/llama-3.1-8b-instruct',
|
|
63
|
+
ollama: 'llama3.3:70b',
|
|
64
|
+
custom: 'custom-model',
|
|
65
|
+
};
|
|
66
|
+
document.getElementById('mobile-model-input').value = modelMap[prov] || 'custom';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Mode Switcher
|
|
70
|
+
function switchMode(mode, save = true) {
|
|
71
|
+
mobileConfig.mode = mode;
|
|
72
|
+
const btnVibe = document.getElementById('btn-mode-vibe');
|
|
73
|
+
const btnPlan = document.getElementById('btn-mode-plan');
|
|
74
|
+
|
|
75
|
+
if (mode === 'plan') {
|
|
76
|
+
btnPlan.classList.add('active');
|
|
77
|
+
btnVibe.classList.remove('active');
|
|
78
|
+
} else {
|
|
79
|
+
btnVibe.classList.add('active');
|
|
80
|
+
btnPlan.classList.remove('active');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (save) {
|
|
84
|
+
localStorage.setItem('antri_mobile_settings', JSON.stringify(mobileConfig));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Navigation View Switcher
|
|
89
|
+
function switchMobileView(viewName) {
|
|
90
|
+
document.querySelectorAll('.mobile-view').forEach((el) => el.classList.remove('active'));
|
|
91
|
+
document.querySelectorAll('.nav-btn').forEach((el) => el.classList.remove('active'));
|
|
92
|
+
|
|
93
|
+
const target = document.getElementById(`view-${viewName}`);
|
|
94
|
+
if (target) target.classList.add('active');
|
|
95
|
+
|
|
96
|
+
const views = ['chat', 'dialectic', 'goal', 'profiles', 'memory', 'settings'];
|
|
97
|
+
const idx = views.indexOf(viewName);
|
|
98
|
+
const btns = document.querySelectorAll('.nav-btn');
|
|
99
|
+
if (btns[idx]) btns[idx].classList.add('active');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Mobile File & Image Uploads (+)
|
|
103
|
+
function handleMobileFile(event) {
|
|
104
|
+
const files = event.target.files;
|
|
105
|
+
if (!files || files.length === 0) return;
|
|
106
|
+
|
|
107
|
+
for (const file of files) {
|
|
108
|
+
const reader = new FileReader();
|
|
109
|
+
reader.onload = (e) => {
|
|
110
|
+
attachedMobileFiles.push({
|
|
111
|
+
name: file.name,
|
|
112
|
+
type: file.type,
|
|
113
|
+
isImage: file.type.startsWith('image/'),
|
|
114
|
+
dataUrl: e.target.result,
|
|
115
|
+
});
|
|
116
|
+
renderMobileAttachmentTray();
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
if (file.type.startsWith('image/')) {
|
|
120
|
+
reader.readAsDataURL(file);
|
|
121
|
+
} else {
|
|
122
|
+
reader.readAsText(file);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
event.target.value = '';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function renderMobileAttachmentTray() {
|
|
130
|
+
const tray = document.getElementById('mobile-attachment-tray');
|
|
131
|
+
if (!tray) return;
|
|
132
|
+
|
|
133
|
+
if (attachedMobileFiles.length === 0) {
|
|
134
|
+
tray.classList.add('hidden');
|
|
135
|
+
tray.innerHTML = '';
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
tray.classList.remove('hidden');
|
|
140
|
+
tray.innerHTML = '';
|
|
141
|
+
|
|
142
|
+
attachedMobileFiles.forEach((file, index) => {
|
|
143
|
+
const chip = document.createElement('div');
|
|
144
|
+
chip.className = 'mobile-attach-chip';
|
|
145
|
+
if (file.isImage) {
|
|
146
|
+
chip.innerHTML = `
|
|
147
|
+
<img src="${file.dataUrl}" alt="thumbnail" />
|
|
148
|
+
<span>${file.name}</span>
|
|
149
|
+
<button style="background:none;border:none;font-weight:700;" onclick="removeMobileAttachment(${index})">×</button>
|
|
150
|
+
`;
|
|
151
|
+
} else {
|
|
152
|
+
chip.innerHTML = `
|
|
153
|
+
<span>${file.name}</span>
|
|
154
|
+
<button style="background:none;border:none;font-weight:700;" onclick="removeMobileAttachment(${index})">×</button>
|
|
155
|
+
`;
|
|
156
|
+
}
|
|
157
|
+
tray.appendChild(chip);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function removeMobileAttachment(index) {
|
|
162
|
+
attachedMobileFiles.splice(index, 1);
|
|
163
|
+
renderMobileAttachmentTray();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function autoGrowTextarea(element) {
|
|
167
|
+
element.style.height = '5px';
|
|
168
|
+
element.style.height = element.scrollHeight + 'px';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Chat Prompt Submission
|
|
172
|
+
async function submitMobilePrompt() {
|
|
173
|
+
const input = document.getElementById('mobile-prompt-input');
|
|
174
|
+
let prompt = input.value.trim();
|
|
175
|
+
|
|
176
|
+
if (attachedMobileFiles.length > 0) {
|
|
177
|
+
const attachDesc = attachedMobileFiles.map((f) => `\n[Attached File: ${f.name} (${f.type})]`).join('');
|
|
178
|
+
prompt = prompt + attachDesc;
|
|
179
|
+
attachedMobileFiles = [];
|
|
180
|
+
renderMobileAttachmentTray();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!prompt) return;
|
|
184
|
+
|
|
185
|
+
input.value = '';
|
|
186
|
+
input.style.height = 'auto';
|
|
187
|
+
|
|
188
|
+
// Check for /debate or /goal
|
|
189
|
+
if (prompt.startsWith('/debate')) {
|
|
190
|
+
switchMobileView('dialectic');
|
|
191
|
+
document.getElementById('mobile-debate-query').value = prompt.replace('/debate', '').trim();
|
|
192
|
+
startMobileDebate();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (prompt.startsWith('/goal') || prompt.startsWith('/loop')) {
|
|
196
|
+
switchMobileView('goal');
|
|
197
|
+
document.getElementById('mobile-goal-objective').value = prompt.replace(/^\/(goal|loop)/, '').trim();
|
|
198
|
+
startMobileGoalLoop();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
appendMobileMessage('user', prompt);
|
|
203
|
+
|
|
204
|
+
const assistantMsgEl = appendMobileMessage('assistant', '');
|
|
205
|
+
const contentEl = assistantMsgEl.querySelector('.msg-content');
|
|
206
|
+
const sendBtn = document.getElementById('mobile-send-btn');
|
|
207
|
+
sendBtn.disabled = true;
|
|
208
|
+
sendBtn.textContent = 'Thinking...';
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
// Connect to local mobile endpoint or direct provider
|
|
212
|
+
const res = await fetch('/api/chat', {
|
|
213
|
+
method: 'POST',
|
|
214
|
+
headers: { 'Content-Type': 'application/json' },
|
|
215
|
+
body: JSON.stringify({ prompt, config: mobileConfig }),
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
if (res.headers.get('content-type')?.includes('text/event-stream')) {
|
|
219
|
+
const reader = res.body.getReader();
|
|
220
|
+
const decoder = new TextDecoder();
|
|
221
|
+
let accumulated = '';
|
|
222
|
+
|
|
223
|
+
while (true) {
|
|
224
|
+
const { done, value } = await reader.read();
|
|
225
|
+
if (done) break;
|
|
226
|
+
|
|
227
|
+
const chunk = decoder.decode(value);
|
|
228
|
+
const lines = chunk.split('\n');
|
|
229
|
+
|
|
230
|
+
for (const line of lines) {
|
|
231
|
+
if (line.startsWith('data: ')) {
|
|
232
|
+
try {
|
|
233
|
+
const data = JSON.parse(line.slice(6));
|
|
234
|
+
if (data.token) {
|
|
235
|
+
accumulated += data.token;
|
|
236
|
+
contentEl.textContent = accumulated;
|
|
237
|
+
scrollMobileChat();
|
|
238
|
+
}
|
|
239
|
+
} catch (e) {}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
const data = await res.json();
|
|
245
|
+
contentEl.textContent = data.response || data.error || 'Response received.';
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Save interaction to local episodic memory
|
|
249
|
+
mobileEpisodes.push({ query: prompt, response: contentEl.textContent, timestamp: Date.now() });
|
|
250
|
+
saveMobileMemory();
|
|
251
|
+
} catch (err) {
|
|
252
|
+
contentEl.textContent = `Offline / Error: ${err.message}. Check Settings for valid API key.`;
|
|
253
|
+
} finally {
|
|
254
|
+
sendBtn.disabled = false;
|
|
255
|
+
sendBtn.textContent = 'Send';
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function appendMobileMessage(role, text) {
|
|
260
|
+
const container = document.getElementById('chat-messages');
|
|
261
|
+
const row = document.createElement('div');
|
|
262
|
+
row.className = `msg-row ${role}`;
|
|
263
|
+
const content = document.createElement('div');
|
|
264
|
+
content.className = 'msg-content';
|
|
265
|
+
content.textContent = text;
|
|
266
|
+
row.appendChild(content);
|
|
267
|
+
container.appendChild(row);
|
|
268
|
+
scrollMobileChat();
|
|
269
|
+
return row;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function scrollMobileChat() {
|
|
273
|
+
const container = document.getElementById('chat-messages');
|
|
274
|
+
container.scrollTop = container.scrollHeight;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function setPrompt(text) {
|
|
278
|
+
const input = document.getElementById('mobile-prompt-input');
|
|
279
|
+
input.value = text;
|
|
280
|
+
input.focus();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Dialectic Debate Runner
|
|
284
|
+
async function startMobileDebate() {
|
|
285
|
+
const input = document.getElementById('mobile-debate-query');
|
|
286
|
+
const query = input.value.trim();
|
|
287
|
+
if (!query) return;
|
|
288
|
+
|
|
289
|
+
const depth = document.getElementById('mobile-debate-depth').value;
|
|
290
|
+
|
|
291
|
+
document.getElementById('m-dialectic-thesis').textContent = 'Drafting thesis...';
|
|
292
|
+
document.getElementById('m-dialectic-antithesis').textContent = 'Awaiting thesis for critique...';
|
|
293
|
+
document.getElementById('m-dialectic-verification').textContent = 'Verification engine standby...';
|
|
294
|
+
document.getElementById('m-dialectic-synthesis').textContent = 'Consensus will appear here...';
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
const res = await fetch('/api/debate', {
|
|
298
|
+
method: 'POST',
|
|
299
|
+
headers: { 'Content-Type': 'application/json' },
|
|
300
|
+
body: JSON.stringify({ query, depth, config: mobileConfig }),
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const reader = res.body.getReader();
|
|
304
|
+
const decoder = new TextDecoder();
|
|
305
|
+
|
|
306
|
+
while (true) {
|
|
307
|
+
const { done, value } = await reader.read();
|
|
308
|
+
if (done) break;
|
|
309
|
+
|
|
310
|
+
const chunk = decoder.decode(value);
|
|
311
|
+
const lines = chunk.split('\n');
|
|
312
|
+
|
|
313
|
+
for (const line of lines) {
|
|
314
|
+
if (line.startsWith('data: ')) {
|
|
315
|
+
try {
|
|
316
|
+
const data = JSON.parse(line.slice(6));
|
|
317
|
+
if (data.thesis) document.getElementById('m-dialectic-thesis').textContent = data.thesis;
|
|
318
|
+
if (data.antithesis) document.getElementById('m-dialectic-antithesis').textContent = data.antithesis;
|
|
319
|
+
if (data.verification) document.getElementById('m-dialectic-verification').textContent = data.verification;
|
|
320
|
+
if (data.synthesis) document.getElementById('m-dialectic-synthesis').textContent = data.synthesis;
|
|
321
|
+
} catch (e) {}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
} catch (err) {
|
|
326
|
+
document.getElementById('m-dialectic-synthesis').textContent = `Debate error: ${err.message}`;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Goal Loop Runner
|
|
331
|
+
async function startMobileGoalLoop() {
|
|
332
|
+
const input = document.getElementById('mobile-goal-objective');
|
|
333
|
+
const objective = input.value.trim();
|
|
334
|
+
if (!objective) return;
|
|
335
|
+
|
|
336
|
+
document.getElementById('m-goal-stage-1').textContent = 'Drafting plan & code...';
|
|
337
|
+
document.getElementById('m-goal-stage-2').textContent = 'Adversarial review standby...';
|
|
338
|
+
document.getElementById('m-goal-stage-3').textContent = 'Final synthesis standby...';
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
const res = await fetch('/api/goal', {
|
|
342
|
+
method: 'POST',
|
|
343
|
+
headers: { 'Content-Type': 'application/json' },
|
|
344
|
+
body: JSON.stringify({ objective, config: mobileConfig }),
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
const reader = res.body.getReader();
|
|
348
|
+
const decoder = new TextDecoder();
|
|
349
|
+
|
|
350
|
+
while (true) {
|
|
351
|
+
const { done, value } = await reader.read();
|
|
352
|
+
if (done) break;
|
|
353
|
+
|
|
354
|
+
const chunk = decoder.decode(value);
|
|
355
|
+
const lines = chunk.split('\n');
|
|
356
|
+
|
|
357
|
+
for (const line of lines) {
|
|
358
|
+
if (line.startsWith('data: ')) {
|
|
359
|
+
try {
|
|
360
|
+
const data = JSON.parse(line.slice(6));
|
|
361
|
+
if (data.draft) document.getElementById('m-goal-stage-1').textContent = data.draft;
|
|
362
|
+
if (data.critique) document.getElementById('m-goal-stage-2').textContent = data.critique;
|
|
363
|
+
if (data.finalOutput) document.getElementById('m-goal-stage-3').textContent = data.finalOutput;
|
|
364
|
+
} catch (e) {}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
} catch (err) {
|
|
369
|
+
document.getElementById('m-goal-stage-3').textContent = `Goal error: ${err.message}`;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Mobile Standalone Profiles
|
|
374
|
+
function loadMobileProfiles() {
|
|
375
|
+
const saved = localStorage.getItem('antri_mobile_profiles');
|
|
376
|
+
if (saved) {
|
|
377
|
+
try {
|
|
378
|
+
mobileProfiles = JSON.parse(saved);
|
|
379
|
+
} catch (e) {
|
|
380
|
+
mobileProfiles = {};
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (Object.keys(mobileProfiles).length === 0) {
|
|
385
|
+
mobileProfiles['mobile_profile_1'] = '# Mobile Thinking Profile\n\n- Preferred Language: TypeScript\n- Architecture: Modular & Clean\n- Formatting: 2 spaces, strict types';
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const select = document.getElementById('mobile-profile-select');
|
|
389
|
+
select.innerHTML = '';
|
|
390
|
+
|
|
391
|
+
Object.keys(mobileProfiles).forEach((name) => {
|
|
392
|
+
const opt = document.createElement('option');
|
|
393
|
+
opt.value = name;
|
|
394
|
+
opt.textContent = `${name}.md`;
|
|
395
|
+
if (name === activeProfileName) opt.selected = true;
|
|
396
|
+
select.appendChild(opt);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
document.getElementById('mobile-profile-title').textContent = `${activeProfileName}.md`;
|
|
400
|
+
document.getElementById('mobile-profile-editor').value = mobileProfiles[activeProfileName] || '';
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function onMobileProfileSelect(name) {
|
|
404
|
+
activeProfileName = name;
|
|
405
|
+
document.getElementById('mobile-profile-title').textContent = `${name}.md`;
|
|
406
|
+
document.getElementById('mobile-profile-editor').value = mobileProfiles[name] || '';
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function createMobileProfile() {
|
|
410
|
+
const name = prompt('Enter new profile name:');
|
|
411
|
+
if (!name) return;
|
|
412
|
+
const cleanName = name.trim().toLowerCase().replace(/[^a-z0-9_-]/g, '_');
|
|
413
|
+
mobileProfiles[cleanName] = `# ${cleanName} Thinking Profile\n\n- Custom preferences and rules`;
|
|
414
|
+
activeProfileName = cleanName;
|
|
415
|
+
localStorage.setItem('antri_mobile_profiles', JSON.stringify(mobileProfiles));
|
|
416
|
+
loadMobileProfiles();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function saveMobileProfile() {
|
|
420
|
+
const content = document.getElementById('mobile-profile-editor').value;
|
|
421
|
+
mobileProfiles[activeProfileName] = content;
|
|
422
|
+
localStorage.setItem('antri_mobile_profiles', JSON.stringify(mobileProfiles));
|
|
423
|
+
alert('Profile saved to device.');
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Mobile Memory
|
|
427
|
+
function loadMobileMemory() {
|
|
428
|
+
const savedEps = localStorage.getItem('antri_mobile_episodes');
|
|
429
|
+
if (savedEps) {
|
|
430
|
+
try {
|
|
431
|
+
mobileEpisodes = JSON.parse(savedEps);
|
|
432
|
+
} catch (e) {}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const epList = document.getElementById('mobile-episodic-list');
|
|
436
|
+
if (mobileEpisodes.length === 0) {
|
|
437
|
+
epList.textContent = 'No recent episodes.';
|
|
438
|
+
} else {
|
|
439
|
+
epList.innerHTML = mobileEpisodes
|
|
440
|
+
.slice(-5)
|
|
441
|
+
.map((ep) => `<div style="margin-bottom:0.6rem;border-bottom:1px solid var(--border-light);padding-bottom:0.4rem;"><b>Q:</b> ${ep.query}<br/><span style="color:var(--text-tertiary);">${(ep.response || '').slice(0, 80)}...</span></div>`)
|
|
442
|
+
.join('');
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function saveMobileMemory() {
|
|
447
|
+
localStorage.setItem('antri_mobile_episodes', JSON.stringify(mobileEpisodes.slice(-50)));
|
|
448
|
+
loadMobileMemory();
|
|
449
|
+
}
|