feat: extend Control UI assistant identity
This commit is contained in:
@@ -3,84 +3,16 @@ import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import { DEFAULT_ASSISTANT_IDENTITY, resolveAssistantIdentity } from "./assistant-identity.js";
|
||||
|
||||
const ROOT_PREFIX = "/";
|
||||
const AVATAR_PREFIX = "/avatar";
|
||||
|
||||
// === Assistant Identity Resolution ===
|
||||
|
||||
const DEFAULT_ASSISTANT_IDENTITY = {
|
||||
name: "Assistant",
|
||||
avatar: "A",
|
||||
};
|
||||
|
||||
interface AssistantIdentity {
|
||||
name: string;
|
||||
avatar: string;
|
||||
}
|
||||
|
||||
interface AssistantConfig {
|
||||
name?: string;
|
||||
avatar?: string;
|
||||
}
|
||||
|
||||
function parseIdentityMd(content: string): Partial<AssistantIdentity> {
|
||||
const result: Partial<AssistantIdentity> = {};
|
||||
const nameMatch = content.match(/^-\s*Name:\s*(.+)$/im);
|
||||
if (nameMatch?.[1]) {
|
||||
const name = nameMatch[1].trim();
|
||||
if (name && name.length <= 50) {
|
||||
result.name = name;
|
||||
}
|
||||
}
|
||||
const emojiMatch = content.match(/^-\s*Emoji:\s*(.+)$/im);
|
||||
if (emojiMatch?.[1]) {
|
||||
const emoji = emojiMatch[1].trim();
|
||||
if (emoji) {
|
||||
result.avatar = emoji;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function resolveAssistantIdentity(opts?: {
|
||||
configAssistant?: AssistantConfig;
|
||||
workspaceDir?: string;
|
||||
}): AssistantIdentity {
|
||||
const { configAssistant, workspaceDir } = opts ?? {};
|
||||
let name = DEFAULT_ASSISTANT_IDENTITY.name;
|
||||
let avatar = DEFAULT_ASSISTANT_IDENTITY.avatar;
|
||||
|
||||
// Try IDENTITY.md from workspace
|
||||
if (workspaceDir) {
|
||||
try {
|
||||
const identityPath = path.join(workspaceDir, "IDENTITY.md");
|
||||
if (fs.existsSync(identityPath)) {
|
||||
const identityMd = parseIdentityMd(fs.readFileSync(identityPath, "utf8"));
|
||||
if (identityMd.name) name = identityMd.name;
|
||||
if (identityMd.avatar) avatar = identityMd.avatar;
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors reading IDENTITY.md
|
||||
}
|
||||
}
|
||||
|
||||
// Config overrides IDENTITY.md
|
||||
if (configAssistant?.name?.trim()) name = configAssistant.name.trim();
|
||||
if (configAssistant?.avatar?.trim()) avatar = configAssistant.avatar.trim();
|
||||
|
||||
return { name, avatar };
|
||||
}
|
||||
|
||||
// === End Assistant Identity ===
|
||||
|
||||
export type ControlUiRequestOptions = {
|
||||
basePath?: string;
|
||||
config?: {
|
||||
ui?: {
|
||||
assistant?: AssistantConfig;
|
||||
};
|
||||
};
|
||||
workspaceDir?: string;
|
||||
config?: ClawdbotConfig;
|
||||
agentId?: string;
|
||||
};
|
||||
|
||||
export function normalizeControlUiBasePath(basePath?: string): string {
|
||||
@@ -252,8 +184,12 @@ function injectControlUiConfig(html: string, opts: ControlUiInjectionOpts): stri
|
||||
const script =
|
||||
`<script>` +
|
||||
`window.__CLAWDBOT_CONTROL_UI_BASE_PATH__=${JSON.stringify(basePath)};` +
|
||||
`window.__CLAWDBOT_ASSISTANT_NAME__=${JSON.stringify(assistantName ?? "Assistant")};` +
|
||||
`window.__CLAWDBOT_ASSISTANT_AVATAR__=${JSON.stringify(assistantAvatar ?? "A")};` +
|
||||
`window.__CLAWDBOT_ASSISTANT_NAME__=${JSON.stringify(
|
||||
assistantName ?? DEFAULT_ASSISTANT_IDENTITY.name,
|
||||
)};` +
|
||||
`window.__CLAWDBOT_ASSISTANT_AVATAR__=${JSON.stringify(
|
||||
assistantAvatar ?? DEFAULT_ASSISTANT_IDENTITY.avatar,
|
||||
)};` +
|
||||
`</script>`;
|
||||
// Check if already injected
|
||||
if (html.includes("__CLAWDBOT_ASSISTANT_NAME__")) return html;
|
||||
@@ -266,16 +202,15 @@ function injectControlUiConfig(html: string, opts: ControlUiInjectionOpts): stri
|
||||
|
||||
interface ServeIndexHtmlOpts {
|
||||
basePath: string;
|
||||
config?: ControlUiRequestOptions["config"];
|
||||
workspaceDir?: string;
|
||||
config?: ClawdbotConfig;
|
||||
agentId?: string;
|
||||
}
|
||||
|
||||
function serveIndexHtml(res: ServerResponse, indexPath: string, opts: ServeIndexHtmlOpts) {
|
||||
const { basePath, config, workspaceDir } = opts;
|
||||
const identity = resolveAssistantIdentity({
|
||||
configAssistant: config?.ui?.assistant,
|
||||
workspaceDir,
|
||||
});
|
||||
const { basePath, config, agentId } = opts;
|
||||
const identity = config
|
||||
? resolveAssistantIdentity({ cfg: config, agentId })
|
||||
: DEFAULT_ASSISTANT_IDENTITY;
|
||||
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
||||
res.setHeader("Cache-Control", "no-cache");
|
||||
const raw = fs.readFileSync(indexPath, "utf8");
|
||||
@@ -364,7 +299,11 @@ export function handleControlUiHttpRequest(
|
||||
|
||||
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
|
||||
if (path.basename(filePath) === "index.html") {
|
||||
serveIndexHtml(res, filePath, { basePath, config: opts?.config, workspaceDir: opts?.workspaceDir });
|
||||
serveIndexHtml(res, filePath, {
|
||||
basePath,
|
||||
config: opts?.config,
|
||||
agentId: opts?.agentId,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
serveFile(res, filePath);
|
||||
@@ -374,7 +313,11 @@ export function handleControlUiHttpRequest(
|
||||
// SPA fallback (client-side router): serve index.html for unknown paths.
|
||||
const indexPath = path.join(root, "index.html");
|
||||
if (fs.existsSync(indexPath)) {
|
||||
serveIndexHtml(res, indexPath, { basePath, config: opts?.config, workspaceDir: opts?.workspaceDir });
|
||||
serveIndexHtml(res, indexPath, {
|
||||
basePath,
|
||||
config: opts?.config,
|
||||
agentId: opts?.agentId,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user