Repository navigation
Expand file tree
/
Copy pathtui.js
More file actions
98 lines (95 loc) · 6.03 KB
/
Copy pathtui.js
File metadata and controls
98 lines (95 loc) · 6.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import { createRuntime } from "./src/runtime.js";
import { compactStatus, formatAccounts, formatLimits, formatStatus } from "./src/display.js";
import { safeText } from "./src/state.js";
export function sessionProvider(api, sessionID) {
if (sessionID) {
const messages = api.state.session.messages(sessionID);
for (let index = messages.length - 1; index >= 0; index--) {
const message = messages[index];
if (message.role === "user" && message.model?.providerID) return message.model.providerID;
if (message.role === "assistant" && message.providerID) return message.providerID;
}
}
return api.state.config.model?.split("/")[0];
}
export function initialiseMuseTui(api, solid, runtime = createRuntime({ directory: api.state.path.directory })) {
const [view, setView] = solid.createSignal();
let pending = false;
let disposed = false;
async function refresh() {
if (pending || disposed) return;
pending = true;
try { const next = await runtime.status(); if (!disposed) setView(next); }
catch { if (!disposed) setView(undefined); }
finally { pending = false; }
}
const alert = (title, message) => api.ui.dialog.replace(() => api.ui.DialogAlert({ title, message, onConfirm: () => api.ui.dialog.clear() }));
const prompt = (title, onConfirm) => api.ui.dialog.replace(() => api.ui.DialogPrompt({ title,
onConfirm: (value) => { api.ui.dialog.clear(); void Promise.resolve(onConfirm(value)).catch((error) => alert("Muse", safeText(error.message))); } }));
const show = async (title, action, format = (value) => JSON.stringify(value, null, 2)) => {
try { const value = await action(); await refresh(); if (!disposed) alert(title, format(value)); }
catch (error) { if (!disposed) alert(title, safeText(error.message)); }
};
const select = async (scope = "project") => {
const status = await runtime.status();
api.ui.dialog.replace(() => api.ui.DialogSelect({ title: scope === "global" ? "Global Muse account" : "Project Muse account",
options: status.accounts.filter((account) => account.enabled).map((account) => ({ title: account.label, value: account.id,
description: `${account.login}${account.blocked ? " · quota exhausted" : ""}` })), current: status.active,
onSelect: (option) => { void show("Muse account", () => runtime.switchProfile(option.value, scope)); } }));
};
void refresh();
const timer = setInterval(() => { void refresh(); }, 2000);
api.slots.register({ slots: {
session_prompt_right: (_context, props) => {
const node = solid.createElement("text");
const visible = () => sessionProvider(api, props.session_id) === "muse-code";
solid.spread(node, {
get content() { return visible() && view() ? compactStatus(view(), Math.max(15, Math.min(100, api.renderer.width - 45))) : ""; },
get fg() {
const account = view()?.accounts.find((row) => row.active);
if (account?.blocked) return api.theme.current.error;
if (account?.quota?.stale || account?.quota?.window?.usedPercent >= 80 || account?.quota?.weekly?.usedPercent >= 80) return api.theme.current.warning;
return api.theme.current.textMuted;
}, alignSelf: "flex-start", selectable: false, truncate: true, wrapMode: "none",
}, false);
return node;
},
} });
const command = (name, title, onSelect) => ({ title, value: `muse.${name}`, category: "Muse", slash: { name: `muse-${name}` }, onSelect });
const disposeCommands = api.command.register(() => [
command("status", "Muse connection status", () => show("Muse status", () => runtime.status(true), formatStatus)),
command("limits", "Muse usage limits", () => show("Muse quota", () => runtime.status(), formatLimits)),
command("doctor", "Diagnose Muse connectivity", () => show("Muse doctor", () => runtime.doctor())),
command("metrics", "Muse usage metrics", () => show("Muse metrics", () => runtime.metrics())),
command("accounts", "Muse accounts", () => show("Muse accounts", () => runtime.status(), formatAccounts)),
command("switch", "Switch project Muse account", () => select()),
command("switch-global", "Switch global Muse account", () => select("global")),
command("inherit", "Use the global Muse account", () => show("Muse account scope", () => runtime.switchProfile(undefined, "inherit"))),
command("label", "Rename the active Muse account", () => prompt("Account label", async (label) => {
const status = await runtime.status(); await runtime.editProfile(status.active, { label }); await refresh();
})),
command("auto", "Muse automatic account switching", () => {
api.ui.dialog.replace(() => api.ui.DialogSelect({ title: "Switch project account when quota is exhausted",
options: [{ title: "On", value: true }, { title: "Off", value: false }, { title: "Inherit global preference", value: null }],
onSelect: (option) => { void show("Muse automatic switching", () => runtime.configureAutoSwitch(option.value), formatStatus); } }));
}),
command("add", "Add a Muse account", () => prompt("New Muse account label", async (label) => {
const account = await runtime.addProfile(label);
const login = await runtime.beginLogin(account.id);
alert("Connect a Meta account", `${login.url}\n\nCode: ${login.code}\n\nApprove the code in your browser to select this account for the current project.`);
void runtime.awaitLogin(login.id).then((result) => {
if (!disposed) { void refresh(); api.ui.toast({ title: "Muse Code", variant: result.granted ? "success" : "error",
message: result.granted ? "Muse account connected." : "Muse sign-in did not complete." }); }
});
})),
]);
api.lifecycle.onDispose(async () => { disposed = true; clearInterval(timer); disposeCommands(); await runtime.dispose(); });
return { refresh };
}
export default {
id: "opencode-muse-multi-auth.status",
async tui(api) {
const [{ createElement, spread }, { createSignal }] = await Promise.all([import("@opentui/solid"), import("solid-js")]);
initialiseMuseTui(api, { createElement, spread, createSignal });
},
};