fix(ai-chat): fix AI Chat and Assistant to work with LM Studio

- Fix response parsing: check report field, not just message
- Pass user query text to LLM instead of ignoring it
- Append user question to diagnostic prompt with system context
- Increase timeout to 120s for LM Studio responses
- Add command_status_from_args to core/common.uc exports

Closes #38
This commit is contained in:
Dushnilin
2026-08-19 15:57:42 +03:00
parent a161ae4845
commit 0964b79171
60 changed files with 616 additions and 1496 deletions
+16 -108
View File
@@ -93,28 +93,28 @@ function singbox_rulesets_module() {
return singbox_rulesets_module_value;
}
function as_string(value) {
return value == null ? "" : "" + value;
}
let as_string = common.as_string;
let shell_quote = common.shell_quote;
let command_from_args = common.command_from_args;
let command_output = common.command_output;
let command_output_from_args = common.command_output_from_args;
let command_status = common.command_status;
let command_success = common.command_success;
let command_success_from_args = common.command_success_from_args;
let read_json_file = common.read_json_file;
let write_json = common.write_json;
let write_file = common.write_file;
let remove_file = common.remove_file;
let object_or_empty = common.object_or_empty;
let array_or_empty = common.array_or_empty;
let option = common.option;
let bool_option = common.bool_option;
function read_stdin() {
let data = fs.readfile("/dev/stdin");
return data == null ? "" : data;
}
function shell_quote(value) {
return "'" + replace(as_string(value), /'/g, "'\\''") + "'";
}
function command_from_args(args) {
let parts = [];
for (let arg in args)
push(parts, shell_quote(arg));
return join(" ", parts);
}
function command_env(assignments) {
let parts = [];
@@ -124,19 +124,6 @@ function command_env(assignments) {
return join(" ", parts);
}
function command_output(command) {
let pipe = fs.popen(command, "r");
if (!pipe)
return "";
let data = pipe.read("all");
let status = pipe.close();
if (status != 0 || data == null)
return "";
return as_string(data);
}
function file_md5(path) {
path = as_string(path);
if (path == "" || fs.stat(path) == null)
@@ -146,28 +133,6 @@ function file_md5(path) {
return length(fields) > 0 ? as_string(fields[0]) : "";
}
function command_output_from_args(args) {
return command_output(command_from_args(args));
}
function command_status(command) {
let status = int(system(command));
if (status == -1)
return 255;
let signal = status & 127;
if (signal != 0)
return 128 + signal;
return (status >> 8) & 255;
}
function command_success(command) {
return command_status(command + " >/dev/null 2>&1") == 0;
}
function command_success_from_args(args) {
return system(command_from_args(args) + " >/dev/null 2>&1") == 0;
}
function command_exists(name) {
return command_success_from_args([ "command", "-v", name ]);
}
@@ -204,31 +169,10 @@ function log_message(message, level) {
command_success_from_args([ "logger", "-t", "tachyon", "[" + level + "] " + as_string(message) ]);
}
function read_json_file(path) {
let data = fs.readfile(path);
if (data == null)
return null;
try {
return json(data);
}
catch (e) {
return null;
}
}
function write_json(value) {
print(sprintf("%J", value), "\n");
}
function json_text(value) {
return sprintf("%J", value) + "\n";
}
function write_file(path, value) {
return fs.writefile(as_string(path), as_string(value)) != null;
}
function write_state_file(path, value) {
path = as_string(path);
let stamp = clock();
@@ -245,17 +189,6 @@ function write_state_file(path, value) {
return true;
}
// The empty catch is the point: every caller means "make sure this path is
// gone", and an absent file already satisfies that. fs.unlink throws on ENOENT,
// so the alternative is a stat() race with no better outcome.
function remove_file(path) {
try {
fs.unlink(as_string(path));
}
catch (e) {
}
}
function file_first_line(path) {
let data = fs.readfile(path);
if (data == null)
@@ -286,10 +219,6 @@ function arg_number(value) {
return int(value);
}
function object_or_empty(value) {
return type(value) == "object" ? value : {};
}
function text_first_chars(value, max_chars) {
value = as_string(value);
max_chars = int(max_chars || "0", 10) || 0;
@@ -312,27 +241,6 @@ function file_last_nonblank_line_value(path, fallback, max_chars) {
return text_first_chars(result, max_chars);
}
function array_or_empty(value) {
return type(value) == "array" ? value : [];
}
function option(section, key, fallback) {
if (fallback == null)
fallback = "";
let value = object_or_empty(section)[key];
if (value == null)
return fallback;
if (type(value) == "array")
return join(" ", value);
return as_string(value);
}
function bool_option(section, key, fallback) {
let value = object_or_empty(section)[key];
return value == null ? !!fallback : arg_bool(value);
}
function section_name(section) {
return as_string(object_or_empty(section)[".name"]);
}