sync
This commit is contained in:
parent
789ff22263
commit
8ce9361d96
3
.env
3
.env
@ -7,7 +7,6 @@ CMD_APPROVE_LEVEL=medium
|
|||||||
PORT=3100
|
PORT=3100
|
||||||
LOG_LEVEL=debug
|
LOG_LEVEL=debug
|
||||||
TEMPERATURE=0.3
|
TEMPERATURE=0.3
|
||||||
WHITELIST=d:\
|
|
||||||
BLACKLIST=
|
|
||||||
DUCKDB_EXE=D:\workbench\miniai\bin\duckdb.exe
|
DUCKDB_EXE=D:\workbench\miniai\bin\duckdb.exe
|
||||||
DUCKDB_FILE=D:\workbench\miniai\data\duck.db
|
DUCKDB_FILE=D:\workbench\miniai\data\duck.db
|
||||||
|
REMOTE_LOG_URL=http://127.0.0.1:3200/log
|
||||||
|
|||||||
10
.env.example
10
.env.example
@ -21,12 +21,10 @@ LOG_LEVEL=debug
|
|||||||
# 温度(0~1)
|
# 温度(0~1)
|
||||||
TEMPERATURE=0.3
|
TEMPERATURE=0.3
|
||||||
|
|
||||||
# 路径白名单(允许的盘符/目录,逗号分隔;留空表示仅工作区)
|
|
||||||
WHITELIST=d:\
|
|
||||||
|
|
||||||
# 路径黑名单(优先级最高,逗号分隔;留空表示无)
|
|
||||||
BLACKLIST=
|
|
||||||
|
|
||||||
# DuckDB:内置 CLI 可执行文件路径与本地数据库文件(无需原生 .node 依赖)
|
# DuckDB:内置 CLI 可执行文件路径与本地数据库文件(无需原生 .node 依赖)
|
||||||
DUCKDB_EXE=bin/duckdb.exe
|
DUCKDB_EXE=bin/duckdb.exe
|
||||||
DUCKDB_FILE=data/duck.db
|
DUCKDB_FILE=data/duck.db
|
||||||
|
|
||||||
|
# 远程日志接收器地址(HTTP,接收方 POST /log 推送日志内容;留空则只写本地日志)
|
||||||
|
# 例如 http://127.0.0.1:3200/log
|
||||||
|
REMOTE_LOG_URL=
|
||||||
|
|||||||
@ -3,9 +3,10 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "concurrently \"vite\" \"bun run node server.ts\"",
|
"dev": "concurrently \"vite\" \"bun run server.ts\"",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"start": "bun run server.ts",
|
"start": "bun run server.ts",
|
||||||
|
"receiver": "bun run remote_receiver.ts",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
86
remote_receiver.ts
Normal file
86
remote_receiver.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// 远程日志接收器
|
||||||
|
// 监听 HTTP 端口,接收日志内容推送(POST /log,JSON 或纯文本),
|
||||||
|
// 按日期追加存入 received_logs/ 文件夹。日志内容直接传输,不做文件传输。
|
||||||
|
import { createServer } from "node:http";
|
||||||
|
import { mkdir, appendFile } from "fs/promises";
|
||||||
|
import { existsSync } from "fs";
|
||||||
|
import { resolve } from "path";
|
||||||
|
|
||||||
|
const PORT = parseInt(process.env.RECEIVER_PORT || "3200", 10);
|
||||||
|
const LOG_DIR = resolve("received_logs");
|
||||||
|
|
||||||
|
function now(iso: string): string {
|
||||||
|
return iso ? iso : new Date().toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function fmtEntry(p: { level?: string; category?: string; message?: string; line?: string; ts?: string }): string {
|
||||||
|
const ts = now(p.ts);
|
||||||
|
const level = (p.level || "info").toUpperCase().padEnd(5);
|
||||||
|
const category = p.category || "receiver";
|
||||||
|
const message = p.message !== undefined ? p.message : "";
|
||||||
|
return `[${ts}] [${level}] [${category}] ${message}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = createServer(async (req, res) => {
|
||||||
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||||
|
res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
|
||||||
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
||||||
|
|
||||||
|
if (req.method === "OPTIONS") { res.writeHead(204); res.end(); return; }
|
||||||
|
|
||||||
|
const url = new URL(req.url || "/", `http://localhost:${PORT}`);
|
||||||
|
|
||||||
|
if (req.method === "GET") {
|
||||||
|
res.writeHead(200, { "Content-Type": "application/json" });
|
||||||
|
res.end(JSON.stringify({ status: "ok", name: "远程日志接收器", receivePath: "POST /log", port: PORT }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.method === "POST" && (url.pathname === "/log" || url.pathname === "/")) {
|
||||||
|
let body = "";
|
||||||
|
try {
|
||||||
|
for await (const c of req) body += c;
|
||||||
|
} catch {
|
||||||
|
res.writeHead(400); res.end("bad request"); return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let entry: string;
|
||||||
|
let level = "info";
|
||||||
|
let category = "receiver";
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(body);
|
||||||
|
if (typeof json === "string") {
|
||||||
|
entry = json;
|
||||||
|
} else if (json.line) {
|
||||||
|
entry = json.line;
|
||||||
|
level = json.level || level;
|
||||||
|
category = json.category || category;
|
||||||
|
} else {
|
||||||
|
entry = fmtEntry({ level: json.level, category: json.category, message: json.message, ts: json.ts });
|
||||||
|
level = json.level || level;
|
||||||
|
category = json.category || category;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
entry = fmtEntry({ level, category, message: body, ts: new Date().toISOString() });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!existsSync(LOG_DIR)) await mkdir(LOG_DIR, { recursive: true });
|
||||||
|
const ts = new Date().toISOString().slice(0, 10);
|
||||||
|
await appendFile(resolve(LOG_DIR, `received-${ts}.log`), entry + "\n", "utf-8");
|
||||||
|
res.writeHead(200, { "Content-Type": "application/json" });
|
||||||
|
res.end(JSON.stringify({ ok: true }));
|
||||||
|
} catch (e) {
|
||||||
|
res.writeHead(500, { "Content-Type": "application/json" });
|
||||||
|
res.end(JSON.stringify({ ok: false, error: (e as any).message }));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.writeHead(404);
|
||||||
|
res.end("Not Found");
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(PORT, () => {
|
||||||
|
console.log(`[remote_receiver] 远程日志接收器已启动: http://127.0.0.1:${PORT}/log (存入 ${LOG_DIR})`);
|
||||||
|
});
|
||||||
91
server.ts
91
server.ts
@ -34,10 +34,11 @@ const config = {
|
|||||||
TEMPERATURE: parseFloat(process.env.TEMPERATURE || ENV.TEMPERATURE || "0.3"),
|
TEMPERATURE: parseFloat(process.env.TEMPERATURE || ENV.TEMPERATURE || "0.3"),
|
||||||
DUCKDB_EXE: process.env.DUCKDB_EXE || ENV.DUCKDB_EXE || resolve("bin", "duckdb.exe"),
|
DUCKDB_EXE: process.env.DUCKDB_EXE || ENV.DUCKDB_EXE || resolve("bin", "duckdb.exe"),
|
||||||
DUCKDB_FILE: process.env.DUCKDB_FILE || ENV.DUCKDB_FILE || resolve("data", "duck.db"),
|
DUCKDB_FILE: process.env.DUCKDB_FILE || ENV.DUCKDB_FILE || resolve("data", "duck.db"),
|
||||||
|
REMOTE_LOG_URL: process.env.REMOTE_LOG_URL || ENV.REMOTE_LOG_URL || "",
|
||||||
};
|
};
|
||||||
|
|
||||||
// 持久化配置到 .env(WORKSPACE 为内部默认值,不在设置页暴露)
|
// 持久化配置到 .env(WORKSPACE 为内部默认值,不在设置页暴露)
|
||||||
const ENV_KEYS = ["BASE_URL", "API_KEY", "MODEL", "WORKSPACE", "CMD_APPROVE_LEVEL", "PORT", "LOG_LEVEL", "TEMPERATURE", "DUCKDB_EXE", "DUCKDB_FILE"] as const;
|
const ENV_KEYS = ["BASE_URL", "API_KEY", "MODEL", "WORKSPACE", "CMD_APPROVE_LEVEL", "PORT", "LOG_LEVEL", "TEMPERATURE", "DUCKDB_EXE", "DUCKDB_FILE", "REMOTE_LOG_URL"] as const;
|
||||||
function saveEnv() {
|
function saveEnv() {
|
||||||
const lines = ENV_KEYS.map((k) => `${k}=${String((config as any)[k])}`);
|
const lines = ENV_KEYS.map((k) => `${k}=${String((config as any)[k])}`);
|
||||||
const header = "# Mini AI Assistant 配置(可由设置页面修改)\n";
|
const header = "# Mini AI Assistant 配置(可由设置页面修改)\n";
|
||||||
@ -58,15 +59,39 @@ function shouldLog(level: string): boolean {
|
|||||||
async function log(level: string, category: string, data: any) {
|
async function log(level: string, category: string, data: any) {
|
||||||
if (!shouldLog(level)) return;
|
if (!shouldLog(level)) return;
|
||||||
const ts = new Date().toISOString();
|
const ts = new Date().toISOString();
|
||||||
const text = typeof s === "string" ? s : safeStringify(data);
|
const text = typeof data === "string" ? data : safeStringify(data);
|
||||||
const line = `[${ts}] [${level.toUpperCase().padEnd(5)}] [${category}] ${text}\n`;
|
const line = `[${ts}] [${level.toUpperCase().padEnd(5)}] [${category}] ${text}\n`;
|
||||||
try {
|
try {
|
||||||
if (!existsSync(LOG_DIR)) await mkdir(LOG_DIR, { recursive: true });
|
if (!existsSync(LOG_DIR)) await mkdir(LOG_DIR, { recursive: true });
|
||||||
await appendFile(`${LOG_DIR}/chat-${ts.slice(0, 10)}.log`, line, "utf-8");
|
|
||||||
await appendFile(`${LOG_DIR}/global.log`, line, "utf-8");
|
await appendFile(`${LOG_DIR}/global.log`, line, "utf-8");
|
||||||
} catch {
|
} catch {
|
||||||
/* 忽略日志写入失败 */
|
/* 忽略日志写入失败 */
|
||||||
}
|
}
|
||||||
|
pushRemote(level, category, text);
|
||||||
|
}
|
||||||
|
// 推送到远程日志接收器(仅配置了地址时启用;失败静默忽略)
|
||||||
|
function pushRemote(level: string, category: string, message: string) {
|
||||||
|
const url = config.REMOTE_LOG_URL;
|
||||||
|
if (!url) return;
|
||||||
|
try {
|
||||||
|
fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ level, category, message, ts: new Date().toISOString() }),
|
||||||
|
}).catch(() => {});
|
||||||
|
} catch {
|
||||||
|
/* 忽略推送失败 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 对话消息列表的可读格式化(供“发送给AI”日志使用)
|
||||||
|
function fmtMessages(msgs: ChatMsg[]): string {
|
||||||
|
return msgs
|
||||||
|
.map((m) => {
|
||||||
|
if (m.role === "tool") return ` [tool ${m.name}] ${clip(m.content ?? "", 600)}`;
|
||||||
|
if (m.tool_calls?.length) return ` [assistant→tool] ${m.tool_calls.map((tc: any) => tc.function?.name).join(", ")}`;
|
||||||
|
return ` [${m.role}] ${clip(m.content ?? "", 1000)}`;
|
||||||
|
})
|
||||||
|
.join("\n");
|
||||||
}
|
}
|
||||||
// 可安全序列化 BigInt 等类型
|
// 可安全序列化 BigInt 等类型
|
||||||
function safeStringify(s: any): string {
|
function safeStringify(s: any): string {
|
||||||
@ -428,19 +453,23 @@ async function runConversation(initialMessages: ChatMsg[], res: any, fromApprova
|
|||||||
sseInit(res);
|
sseInit(res);
|
||||||
let messages = withSystem(initialMessages);
|
let messages = withSystem(initialMessages);
|
||||||
|
|
||||||
// 日志:记录用户/工具输入
|
// 用户输入
|
||||||
log("debug", "conversation_start", {
|
const userInput = initialMessages
|
||||||
fromApproval,
|
.filter((m) => m.role === "user")
|
||||||
messages: messages.map((m) => ({ role: m.role, content: clip(m.content, 800) })),
|
.map((m) => clip(m.content ?? "", 2000))
|
||||||
});
|
.join(" | ");
|
||||||
|
log("info", "对话开始", fromApproval ? "审批后继续" : "新对话");
|
||||||
|
if (userInput) log("info", "用户输入", userInput);
|
||||||
|
|
||||||
// 工具循环(最多 10 轮避免死循环)
|
// 工具循环(最多 10 轮避免死循环)
|
||||||
for (let round = 0; round < 10; round++) {
|
for (let round = 0; round < 10; round++) {
|
||||||
log("debug", "round_start", { round: round + 1, pendingMessages: messages.length });
|
// 发送给 AI 的请求
|
||||||
|
log("info", "发送给AI", `第${round + 1}轮 model=${config.MODEL} temperature=${config.TEMPERATURE}\n${fmtMessages(messages)}`);
|
||||||
const gen = streamLLM(messages);
|
const gen = streamLLM(messages);
|
||||||
let content = "";
|
let content = "";
|
||||||
let toolCall: any = null;
|
let toolCall: any = null;
|
||||||
let errored = false;
|
let errored = false;
|
||||||
|
let llmErr = "";
|
||||||
for await (const ev of gen) {
|
for await (const ev of gen) {
|
||||||
if (ev.type === "token") {
|
if (ev.type === "token") {
|
||||||
content += ev.content;
|
content += ev.content;
|
||||||
@ -449,23 +478,31 @@ async function runConversation(initialMessages: ChatMsg[], res: any, fromApprova
|
|||||||
toolCall = ev.toolcall;
|
toolCall = ev.toolcall;
|
||||||
} else if (ev.type === "error") {
|
} else if (ev.type === "error") {
|
||||||
errored = true;
|
errored = true;
|
||||||
log("error", "llm_error", ev.content);
|
llmErr = ev.content;
|
||||||
|
log("error", "AI错误", ev.content);
|
||||||
sseSend(res, { type: "token", content: "\n\n⚠️ " + ev.content });
|
sseSend(res, { type: "token", content: "\n\n⚠️ " + ev.content });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log("debug", "assistant_round", { round: round + 1, content: clip(content, 1000), wantsTool: !!toolCall });
|
|
||||||
|
|
||||||
if (errored) { sseSend(res, { type: "done" }); res.end(); return; }
|
if (errored) {
|
||||||
|
log("info", "返回界面", "⚠️ " + clip(llmErr, 1000));
|
||||||
|
sseSend(res, { type: "done" });
|
||||||
|
res.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 需要工具调用
|
// 需要工具调用
|
||||||
if (toolCall && toolCall.function?.name) {
|
if (toolCall && toolCall.function?.name) {
|
||||||
const name = toolCall.function.name;
|
const name = toolCall.function.name;
|
||||||
let args = {};
|
let args = {};
|
||||||
try { args = toolCall.function.arguments ? JSON.parse(toolCall.function.arguments) : {}; } catch { args = {}; }
|
try { args = toolCall.function.arguments ? JSON.parse(toolCall.function.arguments) : {}; } catch { args = {}; }
|
||||||
|
// AI 返回:决定调用工具
|
||||||
|
log("info", "AI返回", `调用工具 ${name},参数 ${safeStringify(args)}`);
|
||||||
|
if (content) log("info", "返回界面", clip(content, 2000));
|
||||||
|
|
||||||
if (needsApproval(name, args)) {
|
if (needsApproval(name, args)) {
|
||||||
// 请求审批,保存上下文等待 /api/approve
|
// 请求审批,保存上下文等待 /api/approve
|
||||||
log("info", "approval_required", { tool: name, args });
|
log("info", "执行命令", `${name} 参数 ${safeStringify(args)}(等待用户审批)`);
|
||||||
pendingApproval = {
|
pendingApproval = {
|
||||||
messages: [
|
messages: [
|
||||||
...messages,
|
...messages,
|
||||||
@ -485,10 +522,11 @@ async function runConversation(initialMessages: ChatMsg[], res: any, fromApprova
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 直接执行
|
// 执行命令/工具
|
||||||
log("debug", "tool_call", { name, args });
|
log("info", "执行命令", `${name} 参数 ${safeStringify(args)}`);
|
||||||
const exec = await execTool(name, args);
|
const exec = await execTool(name, args);
|
||||||
log("debug", "tool_result", { name, ok: exec.ok, result: clip(exec.result, 800) });
|
// 执行结果
|
||||||
|
log("info", "执行结果", `ok=${exec.ok} ${clip(exec.result, 1000)}`);
|
||||||
messages = [
|
messages = [
|
||||||
...messages,
|
...messages,
|
||||||
{ role: "assistant", content: content || null, tool_calls: [toolCall] },
|
{ role: "assistant", content: content || null, tool_calls: [toolCall] },
|
||||||
@ -499,13 +537,14 @@ async function runConversation(initialMessages: ChatMsg[], res: any, fromApprova
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 无工具调用,正常结束
|
// 无工具调用,正常结束
|
||||||
log("debug", "assistant", clip(content, 2000));
|
log("info", "AI返回", clip(content, 2000));
|
||||||
|
log("info", "返回界面", clip(content, 2000));
|
||||||
sseSend(res, { type: "done" });
|
sseSend(res, { type: "done" });
|
||||||
res.end();
|
res.end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log("warn", "round_limit", { round: 10 });
|
log("warn", "对话结束", "达到最大工具调用轮数(10),强制结束");
|
||||||
sseSend(res, { type: "token", content: "\n[达到最大工具调用轮数]" });
|
sseSend(res, { type: "token", content: "\n[达到最大工具调用轮数]" });
|
||||||
sseSend(res, { type: "done" });
|
sseSend(res, { type: "done" });
|
||||||
res.end();
|
res.end();
|
||||||
@ -622,6 +661,7 @@ const server = createServer(async (req, res) => {
|
|||||||
CMD_APPROVE_LEVEL: config.CMD_APPROVE_LEVEL,
|
CMD_APPROVE_LEVEL: config.CMD_APPROVE_LEVEL,
|
||||||
LOG_LEVEL: config.LOG_LEVEL,
|
LOG_LEVEL: config.LOG_LEVEL,
|
||||||
TEMPERATURE: config.TEMPERATURE,
|
TEMPERATURE: config.TEMPERATURE,
|
||||||
|
REMOTE_LOG_URL: config.REMOTE_LOG_URL,
|
||||||
}));
|
}));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -632,16 +672,18 @@ const server = createServer(async (req, res) => {
|
|||||||
for await (const c of req) body += c;
|
for await (const c of req) body += c;
|
||||||
let payload: any;
|
let payload: any;
|
||||||
try { payload = JSON.parse(body); } catch { res.writeHead(400); res.end("bad json"); return; }
|
try { payload = JSON.parse(body); } catch { res.writeHead(400); res.end("bad json"); return; }
|
||||||
if (typeof payload.BASE_URL === "string") config.BASE_URL = payload.BASE_URL;
|
if (typeof payload.BASE_URL === "string" && payload.BASE_URL.trim()) config.BASE_URL = payload.BASE_URL.trim();
|
||||||
if (typeof payload.API_KEY === "string" && !payload.API_KEY.startsWith("******")) config.API_KEY = payload.API_KEY;
|
// token 为空时不覆盖已有配置(保留原 token)
|
||||||
if (typeof payload.MODEL === "string") config.MODEL = payload.MODEL;
|
if (typeof payload.API_KEY === "string" && payload.API_KEY.trim() && !payload.API_KEY.startsWith("******")) config.API_KEY = payload.API_KEY.trim();
|
||||||
|
if (typeof payload.MODEL === "string" && payload.MODEL.trim()) config.MODEL = payload.MODEL.trim();
|
||||||
if (payload.CMD_APPROVE_LEVEL === "high" || payload.CMD_APPROVE_LEVEL === "medium" || payload.CMD_APPROVE_LEVEL === "low") {
|
if (payload.CMD_APPROVE_LEVEL === "high" || payload.CMD_APPROVE_LEVEL === "medium" || payload.CMD_APPROVE_LEVEL === "low") {
|
||||||
config.CMD_APPROVE_LEVEL = payload.CMD_APPROVE_LEVEL;
|
config.CMD_APPROVE_LEVEL = payload.CMD_APPROVE_LEVEL;
|
||||||
}
|
}
|
||||||
if (typeof payload.LOG_LEVEL === "string") config.LOG_LEVEL = payload.LOG_LEVEL.toLowerCase();
|
if (typeof payload.LOG_LEVEL === "string") config.LOG_LEVEL = payload.LOG_LEVEL.toLowerCase();
|
||||||
if (typeof payload.TEMPERATURE === "number") config.TEMPERATURE = payload.TEMPERATURE;
|
if (typeof payload.TEMPERATURE === "number") config.TEMPERATURE = payload.TEMPERATURE;
|
||||||
|
if (typeof payload.REMOTE_LOG_URL === "string") config.REMOTE_LOG_URL = payload.REMOTE_LOG_URL.trim();
|
||||||
saveEnv();
|
saveEnv();
|
||||||
log("info", "settings_updated", { CMD_APPROVE_LEVEL: config.CMD_APPROVE_LEVEL, MODEL: config.MODEL, LOG_LEVEL: config.LOG_LEVEL });
|
log("info", "settings_updated", { CMD_APPROVE_LEVEL: config.CMD_APPROVE_LEVEL, MODEL: config.MODEL, LOG_LEVEL: config.LOG_LEVEL, REMOTE_LOG_URL: config.REMOTE_LOG_URL });
|
||||||
res.writeHead(200, { "Content-Type": "application/json" });
|
res.writeHead(200, { "Content-Type": "application/json" });
|
||||||
res.end(JSON.stringify({ ok: true }));
|
res.end(JSON.stringify({ ok: true }));
|
||||||
return;
|
return;
|
||||||
@ -781,10 +823,11 @@ const server = createServer(async (req, res) => {
|
|||||||
|
|
||||||
let exec;
|
let exec;
|
||||||
if (approved) {
|
if (approved) {
|
||||||
log("info", "approval_granted", { tool: name, args });
|
log("info", "执行命令", `${name} 参数 ${safeStringify(args)}(已批准)`);
|
||||||
exec = await execTool(name, args);
|
exec = await execTool(name, args);
|
||||||
|
log("info", "执行结果", `ok=${exec.ok} ${clip(exec.result, 1000)}`);
|
||||||
} else {
|
} else {
|
||||||
log("info", "approval_denied", { tool: name, args });
|
log("info", "执行命令", `${name} 参数 ${safeStringify(args)}(已拒绝)`);
|
||||||
exec = { ok: false, result: "用户已拒绝该操作" };
|
exec = { ok: false, result: "用户已拒绝该操作" };
|
||||||
}
|
}
|
||||||
const nextMessages: ChatMsg[] = [
|
const nextMessages: ChatMsg[] = [
|
||||||
|
|||||||
@ -19,6 +19,11 @@
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<p class="hint">debug 级别会把所有对话、工具调用、审批记录到 <code>logs/</code> 目录。</p>
|
<p class="hint">debug 级别会把所有对话、工具调用、审批记录到 <code>logs/</code> 目录。</p>
|
||||||
|
<label class="row">
|
||||||
|
<span>远程日志地址</span>
|
||||||
|
<input v-model="form.REMOTE_LOG_URL" placeholder="http://127.0.0.1:3200/log,留空则只写本地" />
|
||||||
|
</label>
|
||||||
|
<p class="hint">填写远程日志接收器的 HTTP 地址后,每条日志会同时推送到该地址。</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
@ -86,6 +91,7 @@ const form = reactive({
|
|||||||
CMD_APPROVE_LEVEL: "medium",
|
CMD_APPROVE_LEVEL: "medium",
|
||||||
LOG_LEVEL: "debug",
|
LOG_LEVEL: "debug",
|
||||||
TEMPERATURE: 0.3,
|
TEMPERATURE: 0.3,
|
||||||
|
REMOTE_LOG_URL: "",
|
||||||
});
|
});
|
||||||
const saving = ref(false);
|
const saving = ref(false);
|
||||||
const msg = ref("");
|
const msg = ref("");
|
||||||
@ -126,6 +132,7 @@ onMounted(async () => {
|
|||||||
form.CMD_APPROVE_LEVEL = d.CMD_APPROVE_LEVEL || "medium";
|
form.CMD_APPROVE_LEVEL = d.CMD_APPROVE_LEVEL || "medium";
|
||||||
form.LOG_LEVEL = d.LOG_LEVEL || "debug";
|
form.LOG_LEVEL = d.LOG_LEVEL || "debug";
|
||||||
form.TEMPERATURE = typeof d.TEMPERATURE === "number" ? d.TEMPERATURE : 0.3;
|
form.TEMPERATURE = typeof d.TEMPERATURE === "number" ? d.TEMPERATURE : 0.3;
|
||||||
|
form.REMOTE_LOG_URL = d.REMOTE_LOG_URL || "";
|
||||||
// API_KEY 返回脱敏值,不回填明文
|
// API_KEY 返回脱敏值,不回填明文
|
||||||
} catch {
|
} catch {
|
||||||
msg.value = "加载设置失败";
|
msg.value = "加载设置失败";
|
||||||
@ -147,6 +154,7 @@ async function save() {
|
|||||||
CMD_APPROVE_LEVEL: form.CMD_APPROVE_LEVEL,
|
CMD_APPROVE_LEVEL: form.CMD_APPROVE_LEVEL,
|
||||||
LOG_LEVEL: form.LOG_LEVEL,
|
LOG_LEVEL: form.LOG_LEVEL,
|
||||||
TEMPERATURE: form.TEMPERATURE,
|
TEMPERATURE: form.TEMPERATURE,
|
||||||
|
REMOTE_LOG_URL: form.REMOTE_LOG_URL,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
const d = await r.json();
|
const d = await r.json();
|
||||||
|
|||||||
14
start_receiver.bat
Normal file
14
start_receiver.bat
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
@echo off
|
||||||
|
chcp 65001 >nul
|
||||||
|
cd /d "%~dp0"
|
||||||
|
echo ========================================
|
||||||
|
echo 远程日志接收器 启动中...
|
||||||
|
echo 监听 http://127.0.0.1:3200/log
|
||||||
|
echo 收到的日志存入 received_logs/ 目录
|
||||||
|
echo ========================================
|
||||||
|
if exist "C:\Users\Administrator\.bun\bin\bun.exe" (
|
||||||
|
"C:\Users\Administrator\.bun\bin\bun.exe" run remote_receiver.ts
|
||||||
|
) else (
|
||||||
|
bun run remote_receiver.ts
|
||||||
|
)
|
||||||
|
pause
|
||||||
24
vite.config.ts.timestamp-1786890466785-ca99cd4e8af528.mjs
Normal file
24
vite.config.ts.timestamp-1786890466785-ca99cd4e8af528.mjs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// vite.config.ts
|
||||||
|
import { defineConfig } from "file:///D:/workbench/miniai/node_modules/vite/dist/node/index.js";
|
||||||
|
import vue from "file:///D:/workbench/miniai/node_modules/@vitejs/plugin-vue/dist/index.mjs";
|
||||||
|
var vite_config_default = defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
server: {
|
||||||
|
host: true,
|
||||||
|
port: 5173,
|
||||||
|
proxy: {
|
||||||
|
"/api": {
|
||||||
|
target: "http://127.0.0.1:3000",
|
||||||
|
changeOrigin: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
outDir: "dist",
|
||||||
|
base: "./"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
export {
|
||||||
|
vite_config_default as default
|
||||||
|
};
|
||||||
|
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJEOlxcXFx3b3JrYmVuY2hcXFxcbWluaWFpXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ZpbGVuYW1lID0gXCJEOlxcXFx3b3JrYmVuY2hcXFxcbWluaWFpXFxcXHZpdGUuY29uZmlnLnRzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9EOi93b3JrYmVuY2gvbWluaWFpL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZGVmaW5lQ29uZmlnIH0gZnJvbSBcInZpdGVcIjtcbmltcG9ydCB2dWUgZnJvbSBcIkB2aXRlanMvcGx1Z2luLXZ1ZVwiO1xuXG5leHBvcnQgZGVmYXVsdCBkZWZpbmVDb25maWcoe1xuICBwbHVnaW5zOiBbdnVlKCldLFxuICBzZXJ2ZXI6IHtcbiAgICBob3N0OiB0cnVlLFxuICAgIHBvcnQ6IDUxNzMsXG4gICAgcHJveHk6IHtcbiAgICAgIFwiL2FwaVwiOiB7XG4gICAgICAgIHRhcmdldDogXCJodHRwOi8vMTI3LjAuMC4xOjMwMDBcIixcbiAgICAgICAgY2hhbmdlT3JpZ2luOiB0cnVlLFxuICAgICAgfSxcbiAgICB9LFxuICB9LFxuICBidWlsZDoge1xuICAgIG91dERpcjogXCJkaXN0XCIsXG4gICAgYmFzZTogXCIuL1wiLFxuICB9LFxufSk7Il0sCiAgIm1hcHBpbmdzIjogIjtBQUFpUCxTQUFTLG9CQUFvQjtBQUM5USxPQUFPLFNBQVM7QUFFaEIsSUFBTyxzQkFBUSxhQUFhO0FBQUEsRUFDMUIsU0FBUyxDQUFDLElBQUksQ0FBQztBQUFBLEVBQ2YsUUFBUTtBQUFBLElBQ04sTUFBTTtBQUFBLElBQ04sTUFBTTtBQUFBLElBQ04sT0FBTztBQUFBLE1BQ0wsUUFBUTtBQUFBLFFBQ04sUUFBUTtBQUFBLFFBQ1IsY0FBYztBQUFBLE1BQ2hCO0FBQUEsSUFDRjtBQUFBLEVBQ0Y7QUFBQSxFQUNBLE9BQU87QUFBQSxJQUNMLFFBQVE7QUFBQSxJQUNSLE1BQU07QUFBQSxFQUNSO0FBQ0YsQ0FBQzsiLAogICJuYW1lcyI6IFtdCn0K
|
||||||
Loading…
Reference in New Issue
Block a user