@iola_adm/iola-cli 0.1.68 → 0.1.70
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/package.json +1 -1
- package/src/cli.js +41 -14
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -1296,8 +1296,9 @@ function renderAgentInput(state) {
|
|
|
1296
1296
|
clearAgentInputArea(state);
|
|
1297
1297
|
const prompt = "> ";
|
|
1298
1298
|
const lines = state.buffer.split("\n");
|
|
1299
|
-
const inputLines = [`${prompt}${lines[0] || ""}`, ...lines.slice(1)
|
|
1300
|
-
const
|
|
1299
|
+
const inputLines = [`${prompt}${lines[0] || ""}`, ...lines.slice(1)];
|
|
1300
|
+
const statusLine = truncateTerminalLine(` ${buildAgentStatusLine(state)}`);
|
|
1301
|
+
const cwdLine = colorMuted(statusLine);
|
|
1301
1302
|
const menuLines = [];
|
|
1302
1303
|
if (state.slashOpen) {
|
|
1303
1304
|
const matches = currentSlashMatches(state);
|
|
@@ -1311,11 +1312,11 @@ function renderAgentInput(state) {
|
|
|
1311
1312
|
const absoluteIndex = offset + index;
|
|
1312
1313
|
const selected = absoluteIndex === state.selected;
|
|
1313
1314
|
const marker = selected ? ">" : " ";
|
|
1314
|
-
const row = `${marker} ${visibleMatches[index].command.padEnd(24)} ${visibleMatches[index].description}
|
|
1315
|
+
const row = truncateTerminalLine(`${marker} ${visibleMatches[index].command.padEnd(24)} ${visibleMatches[index].description}`);
|
|
1315
1316
|
menuLines.push(selected ? colorSlashSelection(row) : ` ${row.slice(2)}`);
|
|
1316
1317
|
}
|
|
1317
1318
|
const shownTo = Math.min(offset + visibleLimit, matches.length);
|
|
1318
|
-
menuLines.push(` ↑/↓ выбрать • Enter выполнить • Esc закрыть • ${offset + 1}-${shownTo} из ${matches.length}`);
|
|
1319
|
+
menuLines.push(truncateTerminalLine(` ↑/↓ выбрать • Enter выполнить • Esc закрыть • ${offset + 1}-${shownTo} из ${matches.length}`));
|
|
1319
1320
|
}
|
|
1320
1321
|
}
|
|
1321
1322
|
|
|
@@ -1342,27 +1343,35 @@ function clearAgentInputArea(state = null) {
|
|
|
1342
1343
|
function startActivityIndicator(label = "работаю") {
|
|
1343
1344
|
const doneLabel = "готово";
|
|
1344
1345
|
if (!output.isTTY || process.env.NO_COLOR === "1") {
|
|
1345
|
-
output.write(`${label}
|
|
1346
|
+
output.write(`${formatActivityLine(label)}\n`);
|
|
1346
1347
|
const started = Date.now();
|
|
1347
1348
|
return () => {
|
|
1348
1349
|
const seconds = ((Date.now() - started) / 1000).toFixed(1);
|
|
1349
|
-
output.write(
|
|
1350
|
+
output.write(`${formatActivityLine(doneLabel, seconds)}\n`);
|
|
1350
1351
|
};
|
|
1351
1352
|
}
|
|
1352
1353
|
const started = Date.now();
|
|
1353
1354
|
const render = () => {
|
|
1354
1355
|
const seconds = ((Date.now() - started) / 1000).toFixed(1);
|
|
1355
|
-
output.write(`\r\x1b[2K${colorMuted(
|
|
1356
|
+
output.write(`\r\x1b[2K${colorMuted(formatActivityLine(label, seconds))}`);
|
|
1356
1357
|
};
|
|
1357
1358
|
render();
|
|
1358
1359
|
const timer = setInterval(render, 120);
|
|
1359
1360
|
return () => {
|
|
1360
1361
|
clearInterval(timer);
|
|
1361
1362
|
const seconds = ((Date.now() - started) / 1000).toFixed(1);
|
|
1362
|
-
output.write(`\r\x1b[2K${colorMuted(
|
|
1363
|
+
output.write(`\r\x1b[2K${colorMuted(formatActivityLine(doneLabel, seconds))}\n`);
|
|
1363
1364
|
};
|
|
1364
1365
|
}
|
|
1365
1366
|
|
|
1367
|
+
function formatActivityLine(label, seconds = null) {
|
|
1368
|
+
const columns = Math.max(60, Number(output.columns || 100));
|
|
1369
|
+
const middle = ` ${label}${seconds == null ? "" : ` ${seconds}s`} `;
|
|
1370
|
+
const leftWidth = Math.max(1, Math.floor((columns - visibleLength(middle)) / 3));
|
|
1371
|
+
const rightWidth = Math.max(1, columns - leftWidth - visibleLength(middle));
|
|
1372
|
+
return `${"─".repeat(leftWidth)}${middle}${"─".repeat(rightWidth)}`;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1366
1375
|
function suspendRawInputForCommand(stream) {
|
|
1367
1376
|
if (!stream.isTTY || !stream.isRaw) return () => {};
|
|
1368
1377
|
stream.setRawMode(false);
|
|
@@ -1458,6 +1467,13 @@ function buildAgentStatusLine(state) {
|
|
|
1458
1467
|
return `${cwd} | AI: ${kind}${model} (${ai.name})`;
|
|
1459
1468
|
}
|
|
1460
1469
|
|
|
1470
|
+
function truncateTerminalLine(value) {
|
|
1471
|
+
const columns = Math.max(20, Number(output.columns || 100));
|
|
1472
|
+
const text = String(value).replace(/\r?\n/g, " ");
|
|
1473
|
+
if (visibleLength(text) <= columns) return text;
|
|
1474
|
+
return `${text.slice(0, Math.max(0, columns - 1))}…`;
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1461
1477
|
function compactAgentHistory(history) {
|
|
1462
1478
|
if (history.length <= 8) return history;
|
|
1463
1479
|
const summary = history.slice(0, -6)
|
|
@@ -4077,12 +4093,15 @@ async function listAiModels(provider) {
|
|
|
4077
4093
|
}
|
|
4078
4094
|
|
|
4079
4095
|
const payload = await response.json();
|
|
4080
|
-
const
|
|
4096
|
+
const installed = (payload.models || []).map((model) => ({
|
|
4081
4097
|
id: model.name,
|
|
4082
4098
|
provider: "ollama",
|
|
4083
4099
|
note: model.modified_at ? `updated ${model.modified_at}` : "local",
|
|
4084
4100
|
}));
|
|
4085
|
-
|
|
4101
|
+
const installedIds = new Set(installed.map((model) => model.id));
|
|
4102
|
+
const recommended = getRecommendedOllamaModels("not installed")
|
|
4103
|
+
.filter((model) => !installedIds.has(model.id));
|
|
4104
|
+
return [...installed, ...recommended];
|
|
4086
4105
|
} catch {
|
|
4087
4106
|
return getRecommendedOllamaModels("recommended");
|
|
4088
4107
|
}
|
|
@@ -4137,10 +4156,12 @@ async function listAiModels(provider) {
|
|
|
4137
4156
|
|
|
4138
4157
|
function getRecommendedOllamaModels(notePrefix = "recommended") {
|
|
4139
4158
|
return [
|
|
4140
|
-
{ id: "
|
|
4141
|
-
{ id: "
|
|
4142
|
-
{ id: "
|
|
4143
|
-
{ id: "
|
|
4159
|
+
{ id: "qwen3:1.7b", provider: "ollama", note: `${notePrefix} recommended low RAM` },
|
|
4160
|
+
{ id: "qwen3:4b", provider: "ollama", note: `${notePrefix} recommended balanced` },
|
|
4161
|
+
{ id: "gemma3:1b", provider: "ollama", note: `${notePrefix} Gemma low RAM` },
|
|
4162
|
+
{ id: "gemma3:4b", provider: "ollama", note: `${notePrefix} Gemma balanced` },
|
|
4163
|
+
{ id: "llama3.2:3b", provider: "ollama", note: `${notePrefix} legacy fallback` },
|
|
4164
|
+
{ id: "llama3.2:1b", provider: "ollama", note: `${notePrefix} minimal fallback only` },
|
|
4144
4165
|
];
|
|
4145
4166
|
}
|
|
4146
4167
|
|
|
@@ -6559,7 +6580,10 @@ async function buildAiMessages(question, dataContext, history, options = {}, con
|
|
|
6559
6580
|
const system = [
|
|
6560
6581
|
"Ты терминальный AI-агент городского округа Йошкар-Ола.",
|
|
6561
6582
|
"Отвечай на русском языке естественно и по смыслу запроса пользователя.",
|
|
6583
|
+
"Не смешивай языки. Не выдумывай факты, географию и числа.",
|
|
6584
|
+
"Если пользователь просто здоровается, ответь коротким приветствием и спроси, чем помочь.",
|
|
6562
6585
|
hasDataContext ? "Используй только данные из переданного контекста открытых данных." : "Для обычного диалога отвечай как полноценный AI-ассистент, не перечисляй слои и возможности без запроса пользователя.",
|
|
6586
|
+
hasDataContext ? "" : "Не рассказывай сведения о Йошкар-Оле, школах или детских садах без прямого запроса и контекста данных.",
|
|
6563
6587
|
hasDataContext ? "Если в контексте нет нужных сведений, прямо напиши, что данных недостаточно." : "",
|
|
6564
6588
|
hasDataContext ? "Не выдумывай адреса, телефоны, лицензии и руководителей." : "",
|
|
6565
6589
|
hasDataContext ? "Если отвечаешь по конкретным организациям, укажи источник в конце: слой, название и ИНН." : "",
|
|
@@ -6665,6 +6689,9 @@ async function callOllama(config, messages) {
|
|
|
6665
6689
|
model: config.model || "llama3.2:1b",
|
|
6666
6690
|
messages,
|
|
6667
6691
|
stream: false,
|
|
6692
|
+
options: {
|
|
6693
|
+
temperature: Number(config.temperature ?? 0.1),
|
|
6694
|
+
},
|
|
6668
6695
|
}),
|
|
6669
6696
|
});
|
|
6670
6697
|
} catch {
|