@iola_adm/iola-cli 0.1.69 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +29 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iola_adm/iola-cli",
3
- "version": "0.1.69",
3
+ "version": "0.1.70",
4
4
  "description": "CLI и AI-агент городского округа Йошкар-Ола.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/adm-iola/iola-cli#readme",
package/src/cli.js CHANGED
@@ -1343,27 +1343,35 @@ function clearAgentInputArea(state = null) {
1343
1343
  function startActivityIndicator(label = "работаю") {
1344
1344
  const doneLabel = "готово";
1345
1345
  if (!output.isTTY || process.env.NO_COLOR === "1") {
1346
- output.write(`${label}...\n`);
1346
+ output.write(`${formatActivityLine(label)}\n`);
1347
1347
  const started = Date.now();
1348
1348
  return () => {
1349
1349
  const seconds = ((Date.now() - started) / 1000).toFixed(1);
1350
- output.write(`- ${doneLabel} ${seconds}s\n`);
1350
+ output.write(`${formatActivityLine(doneLabel, seconds)}\n`);
1351
1351
  };
1352
1352
  }
1353
1353
  const started = Date.now();
1354
1354
  const render = () => {
1355
1355
  const seconds = ((Date.now() - started) / 1000).toFixed(1);
1356
- output.write(`\r\x1b[2K${colorMuted(`─ ${label} ${seconds}s`)}`);
1356
+ output.write(`\r\x1b[2K${colorMuted(formatActivityLine(label, seconds))}`);
1357
1357
  };
1358
1358
  render();
1359
1359
  const timer = setInterval(render, 120);
1360
1360
  return () => {
1361
1361
  clearInterval(timer);
1362
1362
  const seconds = ((Date.now() - started) / 1000).toFixed(1);
1363
- output.write(`\r\x1b[2K${colorMuted(`─ ${doneLabel} ${seconds}s`)}\n`);
1363
+ output.write(`\r\x1b[2K${colorMuted(formatActivityLine(doneLabel, seconds))}\n`);
1364
1364
  };
1365
1365
  }
1366
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
+
1367
1375
  function suspendRawInputForCommand(stream) {
1368
1376
  if (!stream.isTTY || !stream.isRaw) return () => {};
1369
1377
  stream.setRawMode(false);
@@ -4085,12 +4093,15 @@ async function listAiModels(provider) {
4085
4093
  }
4086
4094
 
4087
4095
  const payload = await response.json();
4088
- const models = (payload.models || []).map((model) => ({
4096
+ const installed = (payload.models || []).map((model) => ({
4089
4097
  id: model.name,
4090
4098
  provider: "ollama",
4091
4099
  note: model.modified_at ? `updated ${model.modified_at}` : "local",
4092
4100
  }));
4093
- return models.length > 0 ? models : getRecommendedOllamaModels("not installed");
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];
4094
4105
  } catch {
4095
4106
  return getRecommendedOllamaModels("recommended");
4096
4107
  }
@@ -4145,10 +4156,12 @@ async function listAiModels(provider) {
4145
4156
 
4146
4157
  function getRecommendedOllamaModels(notePrefix = "recommended") {
4147
4158
  return [
4148
- { id: "llama3.2:1b", provider: "ollama", note: `${notePrefix} low RAM` },
4149
- { id: "llama3.2:3b", provider: "ollama", note: `${notePrefix} standard` },
4150
- { id: "qwen3:4b", provider: "ollama", note: `${notePrefix} balanced` },
4151
- { id: "qwen3:8b", provider: "ollama", note: `${notePrefix} good GPU` },
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` },
4152
4165
  ];
4153
4166
  }
4154
4167
 
@@ -6567,7 +6580,10 @@ async function buildAiMessages(question, dataContext, history, options = {}, con
6567
6580
  const system = [
6568
6581
  "Ты терминальный AI-агент городского округа Йошкар-Ола.",
6569
6582
  "Отвечай на русском языке естественно и по смыслу запроса пользователя.",
6583
+ "Не смешивай языки. Не выдумывай факты, географию и числа.",
6584
+ "Если пользователь просто здоровается, ответь коротким приветствием и спроси, чем помочь.",
6570
6585
  hasDataContext ? "Используй только данные из переданного контекста открытых данных." : "Для обычного диалога отвечай как полноценный AI-ассистент, не перечисляй слои и возможности без запроса пользователя.",
6586
+ hasDataContext ? "" : "Не рассказывай сведения о Йошкар-Оле, школах или детских садах без прямого запроса и контекста данных.",
6571
6587
  hasDataContext ? "Если в контексте нет нужных сведений, прямо напиши, что данных недостаточно." : "",
6572
6588
  hasDataContext ? "Не выдумывай адреса, телефоны, лицензии и руководителей." : "",
6573
6589
  hasDataContext ? "Если отвечаешь по конкретным организациям, укажи источник в конце: слой, название и ИНН." : "",
@@ -6673,6 +6689,9 @@ async function callOllama(config, messages) {
6673
6689
  model: config.model || "llama3.2:1b",
6674
6690
  messages,
6675
6691
  stream: false,
6692
+ options: {
6693
+ temperature: Number(config.temperature ?? 0.1),
6694
+ },
6676
6695
  }),
6677
6696
  });
6678
6697
  } catch {