Files
webui-develop/static/commands.js

401 lines
15 KiB
JavaScript

(() => {
const PASSTHROUGH = [
"retry",
"undo",
"title",
"branch",
"stop",
"background",
"btw",
"queue",
"status",
"profile",
"resume",
"snapshot",
"rollback",
"provider",
"yolo",
"reasoning",
"fast",
"voice",
"reload",
"reload-mcp",
"cron",
"browser",
"plugins",
"insights",
"platforms",
"debug",
"update",
"image",
"inbox"
];
let COMMANDS = [];
const AGENT_INFO = {
"sunflower": { emoji: "\u{1F33B}", name: "Sunflower", file: "sunflower/soul.md", domain: "Finance, Wealth & Subscriptions" },
"lotus": { emoji: "\u{1F9D7}", name: "Lotus", file: "lotus/soul.md", domain: "Health, Fitness & Recovery" },
"forget-me-not": { emoji: "\u{1F33C}", name: "Forget-me-not", file: "forget-me-not/soul.md", domain: "Calendar, Time & Social" },
"iris": { emoji: "\u2695\uFE0F", name: "Iris", file: "iris/soul.md", domain: "Career, Learning & Focus" },
"ivy": { emoji: "\u{1F33F}", name: "Ivy", file: "ivy/soul.md", domain: "Smart Home & Environment" },
"dandelion": { emoji: "\u{1F6E1}", name: "Dandelion", file: "dandelion/soul.md", domain: "Communication Triage & Gatekeeping" },
"root": { emoji: "\u{1F333}", name: "Root", file: "root/soul.md", domain: "DevOps, Logs & System Health" },
"back": { emoji: "\u{1F339}", name: "Rose", file: "rose/soul.md", domain: "Orchestrator (return from agent)" }
};
function _fnFor(name) {
if (name === "help" || name === "commands") return cmdHelp;
if (name === "clear") return cmdClear;
if (name === "compact" || name === "compress") return cmdCompact;
if (name === "model") return cmdModel;
if (name === "workspace") return cmdWorkspace;
if (name === "new") return cmdNew;
if (name === "usage") return cmdUsage;
if (name === "theme") return cmdTheme;
if (name === "skills") return cmdSkills;
if (name === "personality") return cmdPersonality;
if (PASSTHROUGH.includes(name)) return cmdPassthrough;
return cmdPassthrough;
}
async function loadCommands() {
try {
const data = await api("/api/commands");
if (data.error) throw new Error(data.error);
const cats = data.categories || {};
const merged = [];
for (const [, cmds] of Object.entries(cats)) {
for (const c of cmds) {
merged.push({ name: c.name, desc: c.desc, arg: c.arg || "(none)", aliases: c.aliases || [], fn: _fnFor(c.name) });
}
}
const agentNames = ["sunflower", "lotus", "forget-me-not", "iris", "ivy", "dandelion", "root", "back", "inbox"];
const filtered = merged.filter((c) => !agentNames.includes(c.name));
filtered.push(
{ name: "sunflower", desc: "\u{1F33B} Finance, Wealth & Subscriptions", fn: cmdAgent, arg: "message", aliases: [] },
{ name: "lotus", desc: "\u{1F9D7} Health, Fitness & Recovery", fn: cmdAgent, arg: "message", aliases: [] },
{ name: "forget-me-not", desc: "\u{1F33C} Calendar, Time & Social", fn: cmdAgent, arg: "message", aliases: [] },
{ name: "iris", desc: "\u2695\uFE0F Career, Learning & Focus", fn: cmdAgent, arg: "message", aliases: [] },
{ name: "ivy", desc: "\u{1F33F} Smart Home & Environment", fn: cmdAgent, arg: "message", aliases: [] },
{ name: "dandelion", desc: "\u{1F6E1} Communication Triage & Gatekeeping", fn: cmdAgent, arg: "message", aliases: [] },
{ name: "root", desc: "\u{1F333} DevOps, Logs & System Health", fn: cmdAgent, arg: "message", aliases: [] },
{ name: "back", desc: "\u{1F339} Return to Rose (orchestrator)", fn: cmdAgent, arg: "message", aliases: [] }
);
COMMANDS = filtered;
} catch (e) {
console.warn("[commands] Failed to load from API, using fallback:", e instanceof Error ? e.message : String(e));
COMMANDS = [];
}
}
function parseCommand(text) {
if (!text.startsWith("/")) return null;
const parts = text.slice(1).split(/\s+/);
const name = parts[0].toLowerCase();
const args = parts.slice(1).join(" ").trim();
return { name, args };
}
function executeCommand(text) {
const parsed = parseCommand(text);
if (!parsed) return false;
const cmd = COMMANDS.find((c) => c.name === parsed.name);
if (!cmd) return false;
cmd.fn(parsed.args);
return true;
}
function getMatchingCommands(prefix) {
const q = prefix.toLowerCase();
return COMMANDS.filter((c) => {
if (c.name.startsWith(q)) return true;
if (c.aliases && c.aliases.some((a) => a.startsWith(q))) return true;
return false;
});
}
function cmdPassthrough(_args) {
const msgEl = $("msg");
if (!msgEl) return;
const parsed = parseCommand(msgEl.value);
if (!parsed) return;
send();
}
function cmdHelp() {
const categories = { "Session": [], "Configuration": [], "Tools & Skills": [], "Info": [], "Agents": [] };
COMMANDS.forEach((c) => {
let cat = "Info";
if (["new", "clear", "compact", "compress", "retry", "undo", "title", "branch", "stop", "background", "btw", "queue", "status", "profile", "resume", "snapshot", "rollback"].includes(c.name)) cat = "Session";
else if (["model", "provider", "personality", "workspace", "theme", "yolo", "reasoning", "fast", "voice", "reload", "reload-mcp"].includes(c.name)) cat = "Configuration";
else if (["skills", "cron", "browser", "plugins"].includes(c.name)) cat = "Tools & Skills";
else if (["sunflower", "lotus", "forget-me-not", "iris", "ivy", "dandelion", "root", "back", "inbox"].includes(c.name)) cat = "Agents";
if (!categories[cat]) categories[cat] = [];
categories[cat].push(c);
});
const lines = [];
for (const [cat, cmds] of Object.entries(categories)) {
if (!cmds.length) continue;
lines.push(`
**${cat}**`);
cmds.forEach((c) => {
const usage = c.arg && c.arg !== "(none)" ? ` <${c.arg}>` : "";
lines.push(` /${c.name}${usage} \u2014 ${c.desc}`);
});
}
const msg = { role: "assistant", content: "Available commands:\n" + lines.join("\n") };
S.messages.push(msg);
renderMessages();
showToast(t("type_slash"));
}
function cmdClear() {
if (!S.session) return;
S.messages = [];
S.toolCalls = [];
clearLiveToolCards();
renderMessages();
const emptyState = $("emptyState");
if (emptyState) emptyState.style.display = "";
showToast(t("conversation_cleared"));
}
async function cmdModel(args) {
if (!args) {
showToast("Usage: /model <model_name>");
return;
}
const sel = $("modelSelect");
if (!sel) return;
const q = args.toLowerCase();
let match = null;
for (const opt of sel.options) {
if (opt.value.toLowerCase().includes(q) || (opt.textContent || "").toLowerCase().includes(q)) {
match = opt.value;
break;
}
}
if (!match) {
showToast('No model matching "' + args + '"');
return;
}
sel.value = match;
if (sel.onchange) await sel.onchange(null);
showToast(t("switched_to") + match);
}
async function cmdWorkspace(args) {
if (!args) {
showToast("Usage: /workspace <name>");
return;
}
try {
const data = await api("/api/workspaces");
const q = args.toLowerCase();
const ws = (data.workspaces || []).find(
(w) => (w.name || "").toLowerCase().includes(q) || w.path.toLowerCase().includes(q)
);
if (!ws) {
showToast('No workspace matching "' + args + '"');
return;
}
if (typeof switchToWorkspace === "function") await switchToWorkspace(ws.path, ws.name || ws.path);
else showToast(t("switched_workspace") + (ws.name || ws.path));
} catch (e) {
showToast(t("workspace_switch_failed") + (e instanceof Error ? e.message : String(e)));
}
}
async function cmdNew() {
await newSession();
await renderSessionList();
const msgEl = $("msg");
if (msgEl) msgEl.focus();
showToast(t("new_session"));
}
function cmdCompact() {
const msgEl = $("msg");
if (msgEl) msgEl.value = "Please compress and summarize the conversation context to free up space.";
send();
showToast(t("compressing"));
}
async function cmdUsage() {
const next = !window._showTokenUsage;
window._showTokenUsage = next;
try {
await api("/api/settings", { method: "POST", body: JSON.stringify({ show_token_usage: next }) });
} catch {
}
const cb = $("settingsShowTokenUsage");
if (cb) cb.checked = next;
renderMessages();
showToast(next ? t("token_usage_on") : t("token_usage_off"));
}
async function cmdTheme(args) {
const themes = ["system", "dark", "light", "slate", "solarized", "monokai", "nord", "oled"];
if (!args || !themes.includes(args.toLowerCase())) {
showToast("Themes: " + themes.join(" | "));
return;
}
const themeName = args.toLowerCase();
localStorage.setItem("hermes-theme", themeName);
_applyTheme(themeName);
try {
await api("/api/settings", { method: "POST", body: JSON.stringify({ theme: themeName }) });
} catch {
}
const sel = $("settingsTheme");
if (sel) sel.value = themeName;
showToast(t("theme_set") + themeName);
}
async function cmdSkills(args) {
try {
const data = await api("/api/skills");
let skills = data.skills || [];
if (args) {
const q = args.toLowerCase();
skills = skills.filter(
(s) => (s.name || "").toLowerCase().includes(q) || (s.description || "").toLowerCase().includes(q) || (s.category || "").toLowerCase().includes(q)
);
}
if (!skills.length) {
const msg = { role: "assistant", content: args ? `No skills matching "${args}".` : "No skills found." };
S.messages.push(msg);
renderMessages();
return;
}
const byCategory = {};
skills.forEach((s) => {
const cat = s.category || "General";
if (!byCategory[cat]) byCategory[cat] = [];
byCategory[cat].push(s);
});
const lines = [];
for (const [cat, items] of Object.entries(byCategory).sort()) {
lines.push(`**${cat}**`);
items.forEach((s) => {
const desc = s.description ? ` \u2014 ${s.description.slice(0, 80)}${s.description.length > 80 ? "..." : ""}` : "";
lines.push(` \`${s.name}\`${desc}`);
});
lines.push("");
}
const header = args ? `Skills matching "${args}" (${skills.length}):
` : `Available skills (${skills.length}):
`;
S.messages.push({ role: "assistant", content: header + lines.join("\n") });
renderMessages();
} catch (e) {
showToast("Failed to load skills: " + (e instanceof Error ? e.message : String(e)));
}
}
async function cmdPersonality(args) {
if (!S.session) {
showToast(t("no_active_session"));
return;
}
if (!args) {
try {
const data = await api("/api/personalities");
if (!data.personalities || !data.personalities.length) {
showToast(t("no_personalities"));
return;
}
const list = data.personalities.map((p) => ` **${p.name}**${p.description ? " \u2014 " + p.description : ""}`).join("\n");
S.messages.push({ role: "assistant", content: t("available_personalities") + "\n\n" + list + "\n\nSwitch with: /personality <name>" });
renderMessages();
} catch {
showToast(t("personalities_load_failed"));
}
return;
}
const name = args.trim();
if (["none", "default", "clear"].includes(name.toLowerCase())) {
try {
await api("/api/personality/set", { method: "POST", body: JSON.stringify({ session_id: S.session.session_id, name: "" }) });
showToast(t("personality_cleared"));
} catch (e) {
showToast(t("failed_colon") + (e instanceof Error ? e.message : String(e)));
}
return;
}
try {
await api("/api/personality/set", { method: "POST", body: JSON.stringify({ session_id: S.session.session_id, name }) });
showToast(t("personality_set") + name);
} catch (e) {
showToast(t("failed_colon") + (e instanceof Error ? e.message : String(e)));
}
}
function cmdAgent(_args) {
const msgEl = $("msg");
if (!msgEl) return;
const parsed = parseCommand(msgEl.value);
if (!parsed) return;
const agentKey = parsed.name;
const info = AGENT_INFO[agentKey];
if (!info) {
showToast("Unknown agent: " + agentKey);
return;
}
const userMsg = _args || "";
const contextMsg = `[Agent Switch: ${info.emoji} ${info.name}]
Load ~/.hermes/agents/${info.file} and handle this request as ${info.name} (${info.domain}).${userMsg ? "\n\nUser message: " + userMsg : ""}`;
msgEl.value = contextMsg;
send();
}
let _cmdSelectedIdx = -1;
function showCmdDropdown(matches) {
const dd = $("cmdDropdown");
if (!dd) return;
dd.innerHTML = "";
_cmdSelectedIdx = -1;
for (let i = 0; i < matches.length; i++) {
const c = matches[i];
const el = document.createElement("div");
el.className = "cmd-item";
el.dataset.idx = String(i);
const usage = c.arg && c.arg !== "(none)" ? ` <span class="cmd-item-arg">${esc(c.arg)}</span>` : "";
el.innerHTML = `<div class="cmd-item-name">/${esc(c.name)}${usage}</div><div class="cmd-item-desc">${esc(c.desc)}</div>`;
el.addEventListener("mousedown", (e) => {
e.preventDefault();
const msgEl2 = $("msg");
if (msgEl2) {
msgEl2.value = "/" + c.name + (c.arg && c.arg !== "(none)" ? " " : "");
msgEl2.focus();
}
hideCmdDropdown();
});
dd.appendChild(el);
}
dd.classList.add("open");
}
function hideCmdDropdown() {
const dd = $("cmdDropdown");
if (dd) dd.classList.remove("open");
_cmdSelectedIdx = -1;
}
function navigateCmdDropdown(dir) {
const dd = $("cmdDropdown");
if (!dd) return;
const items = dd.querySelectorAll(".cmd-item");
if (!items.length) return;
items.forEach((el) => el.classList.remove("selected"));
_cmdSelectedIdx += dir;
if (_cmdSelectedIdx < 0) _cmdSelectedIdx = items.length - 1;
if (_cmdSelectedIdx >= items.length) _cmdSelectedIdx = 0;
items[_cmdSelectedIdx].classList.add("selected");
}
function selectCmdDropdownItem() {
const dd = $("cmdDropdown");
if (!dd) return;
const items = dd.querySelectorAll(".cmd-item");
if (_cmdSelectedIdx >= 0 && _cmdSelectedIdx < items.length) {
const item = items[_cmdSelectedIdx];
const ev = new MouseEvent("mousedown", { bubbles: true, cancelable: true });
Object.defineProperty(ev, "preventDefault", { value: () => {
} });
item.dispatchEvent(ev);
} else if (items.length === 1) {
const ev = new MouseEvent("mousedown", { bubbles: true, cancelable: true });
Object.defineProperty(ev, "preventDefault", { value: () => {
} });
items[0].dispatchEvent(ev);
}
hideCmdDropdown();
}
function _applyTheme(themeName) {
document.documentElement.dataset.theme = themeName;
}
window.loadCommands = loadCommands;
})();
//# sourceMappingURL=commands.js.map