feat: update webui extensions, models, and agent config
Some checks failed
CI / build-check-test (push) Has been cancelled
npm audit / audit (push) Has been cancelled

This commit is contained in:
2026-06-10 21:45:53 +08:00
parent cf5edd6394
commit d51a055d78
64 changed files with 3238 additions and 1665 deletions

View File

@@ -4,6 +4,17 @@ import { normalizeChatImages } from "../services/chat-images.ts";
import type { WebUiContext } from "../types/context.ts";
import { json, readBody } from "../http/request.ts";
async function trySlashDispatch(
msg: string,
imgs: ReturnType<typeof normalizeChatImages>,
sendCmd: WebUiContext["rpc"]["sendCmd"],
): Promise<Awaited<ReturnType<typeof dispatchSlashCommand>> | null> {
const trimmed = msg.trim();
if (!trimmed.startsWith("/")) return null;
const slash = await dispatchSlashCommand(trimmed, sendCmd);
return slash?.handled ? slash : null;
}
export function handleChatRoute(
req: IncomingMessage,
res: ServerResponse,
@@ -14,32 +25,66 @@ export function handleChatRoute(
if (req.method === "POST" && pathname === "/api/chat") {
void readBody(req)
.then(async ({ message, images }) => {
.then(async ({ message, images, streamingBehavior }) => {
const msg = typeof message === "string" ? message : "";
const imgs = normalizeChatImages(images);
if (!msg.trim() && !imgs?.length) throw new Error("消息不能为空");
if (imgs?.length) console.log(`[webui] chat: ${imgs.length} image(s), message=${msg.length} chars`);
if (!imgs?.length && msg.trim().startsWith("/")) {
const slash = await dispatchSlashCommand(msg, sendCmd);
if (slash?.handled) {
return json(res, { ok: true, slash: true, ...slash });
}
const slash = await trySlashDispatch(msg, imgs, sendCmd);
if (slash) {
return json(res, { ok: true, slash: true, ...slash });
}
submitPrompt({ message: msg, images: imgs });
const behavior =
streamingBehavior === "steer" || streamingBehavior === "followUp"
? streamingBehavior
: undefined;
submitPrompt({ message: msg, images: imgs, streamingBehavior: behavior });
return json(res, { ok: true, accepted: true }, 202);
})
.catch((err) => json(res, { error: err.message }, 500));
return true;
}
if (req.method === "POST" && pathname === "/api/steer") {
void readBody(req)
.then(async ({ message, images }) => {
const msg = typeof message === "string" ? message.trim() : "";
const imgs = normalizeChatImages(images);
if (!msg && !imgs?.length) throw new Error("消息不能为空");
const result = await sendCmd({ type: "steer", message: msg, images: imgs });
if (!result.success) throw new Error(result.error || "steer 失败");
json(res, { ok: true });
})
.catch((err) => json(res, { error: err.message }, 500));
return true;
}
if (req.method === "POST" && pathname === "/api/follow-up") {
void readBody(req)
.then(async ({ message, images }) => {
const msg = typeof message === "string" ? message.trim() : "";
const imgs = normalizeChatImages(images);
if (!msg && !imgs?.length) throw new Error("消息不能为空");
const result = await sendCmd({ type: "follow_up", message: msg, images: imgs });
if (!result.success) throw new Error(result.error || "follow_up 失败");
json(res, { ok: true });
})
.catch((err) => json(res, { error: err.message }, 500));
return true;
}
if (req.method === "POST" && pathname === "/api/bash") {
void readBody(req)
.then(async ({ command }) => {
.then(async ({ command, excludeFromContext }) => {
const cmd = typeof command === "string" ? command.trim() : "";
if (!cmd) throw new Error("命令不能为空");
const result = await sendCmd({ type: "bash", command: cmd });
const result = await sendCmd({
type: "bash",
command: cmd,
excludeFromContext: excludeFromContext === true,
});
if (!result.success) throw new Error(result.error || "命令执行失败");
json(res, result.data);
})
@@ -72,5 +117,12 @@ export function handleChatRoute(
return true;
}
if (req.method === "POST" && pathname === "/api/abort-retry") {
void sendCmd({ type: "abort_retry" })
.then(() => json(res, { ok: true }))
.catch((err) => json(res, { error: err.message }, 500));
return true;
}
return false;
}

View File

@@ -0,0 +1,25 @@
import type { IncomingMessage, ServerResponse } from "node:http";
import type { WebUiContext } from "../types/context.ts";
import { json, readBody } from "../http/request.ts";
export function handleExtensionUiRoute(
req: IncomingMessage,
res: ServerResponse,
ctx: WebUiContext,
pathname: string,
): boolean {
if (req.method !== "POST" || pathname !== "/api/extension-ui-response") {
return false;
}
void readBody(req)
.then(({ id, response }) => {
if (typeof id !== "string" || !id.trim()) throw new Error("缺少 extension UI 响应 id");
if (!response || typeof response !== "object") throw new Error("缺少 extension UI 响应内容");
ctx.rpc.sendExtensionUiResponse(id, response as Record<string, unknown>);
json(res, { ok: true });
})
.catch((err) => json(res, { error: err.message }, 500));
return true;
}

View File

@@ -9,6 +9,7 @@ import {
resolveSessionFile,
} from "../services/sessions.ts";
import type { WebUiContext } from "../types/context.ts";
import { isSameResolvedPath } from "../utils/paths.ts";
import { json, readBody } from "../http/request.ts";
export function handleSessionsRoute(
@@ -103,7 +104,11 @@ export function handleSessionsRoute(
const savedName = appendSessionName(paths, sp, typeof name === "string" ? name : "");
const sessionPath = resolveSessionFile(paths, sp);
const state = await sendCmd({ type: "get_state" });
if (state.success && state.data?.sessionFile === sessionPath) {
if (
state.success &&
state.data?.sessionFile &&
isSameResolvedPath(String(state.data.sessionFile), sessionPath)
) {
const rename = await sendCmd({ type: "set_session_name", name: savedName });
if (!rename.success) throw new Error(rename.error || "同步当前会话名称失败");
}

View File

@@ -1,4 +1,5 @@
import type { IncomingMessage, ServerResponse } from "node:http";
import { arch, freemem, hostname, platform, release, totalmem, type as osType } from "node:os";
import { readWebuiAvatarSettings, writeWebuiAvatarSettings } from "../db/index.ts";
import { setExtensionEnabled } from "../settings/extension-settings.ts";
import { listMcpSettings, setMcpServerEnabled, setMcpToolEnabled } from "../settings/mcp-settings.ts";
@@ -202,5 +203,23 @@ export function handleSettingsRoute(
return true;
}
if (req.method === "GET" && pathname === "/api/environment") {
json(res, {
nodeVersion: process.version,
platform: platform(),
arch: arch(),
osType: osType(),
osRelease: release(),
hostname: hostname(),
pid: process.pid,
cwd: process.cwd(),
uptime: Math.floor(process.uptime()),
totalMemMb: Math.round(totalmem() / 1024 / 1024),
freeMemMb: Math.round(freemem() / 1024 / 1024),
execPath: process.execPath,
});
return true;
}
return false;
}