UI: improve skill install feedback
This commit is contained in:
committed by
Peter Steinberger
parent
04e0e10411
commit
965615a46c
@@ -60,6 +60,7 @@ import {
|
||||
saveSkillApiKey,
|
||||
updateSkillEdit,
|
||||
updateSkillEnabled,
|
||||
type SkillMessage,
|
||||
} from "./controllers/skills";
|
||||
import { loadNodes } from "./controllers/nodes";
|
||||
import { loadChatHistory } from "./controllers/chat";
|
||||
@@ -166,6 +167,7 @@ export type AppViewState = {
|
||||
skillsError: string | null;
|
||||
skillsFilter: string;
|
||||
skillEdits: Record<string, string>;
|
||||
skillMessages: Record<string, SkillMessage>;
|
||||
skillsBusyKey: string | null;
|
||||
debugLoading: boolean;
|
||||
debugStatus: StatusSummary | null;
|
||||
@@ -391,6 +393,7 @@ export function renderApp(state: AppViewState) {
|
||||
error: state.skillsError,
|
||||
filter: state.skillsFilter,
|
||||
edits: state.skillEdits,
|
||||
messages: state.skillMessages,
|
||||
busyKey: state.skillsBusyKey,
|
||||
onFilterChange: (next) => (state.skillsFilter = next),
|
||||
onRefresh: () => loadSkills(state),
|
||||
|
||||
@@ -78,6 +78,7 @@ import {
|
||||
} from "./controllers/cron";
|
||||
import {
|
||||
loadSkills,
|
||||
type SkillMessage,
|
||||
} from "./controllers/skills";
|
||||
import { loadDebug } from "./controllers/debug";
|
||||
import { loadLogs } from "./controllers/logs";
|
||||
@@ -356,6 +357,7 @@ export class ClawdbotApp extends LitElement {
|
||||
@state() skillsFilter = "";
|
||||
@state() skillEdits: Record<string, string> = {};
|
||||
@state() skillsBusyKey: string | null = null;
|
||||
@state() skillMessages: Record<string, SkillMessage> = {};
|
||||
|
||||
@state() debugLoading = false;
|
||||
@state() debugStatus: StatusSummary | null = null;
|
||||
|
||||
@@ -9,8 +9,24 @@ export type SkillsState = {
|
||||
skillsError: string | null;
|
||||
skillsBusyKey: string | null;
|
||||
skillEdits: Record<string, string>;
|
||||
skillMessages: SkillMessageMap;
|
||||
};
|
||||
|
||||
export type SkillMessage = {
|
||||
kind: "success" | "error";
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type SkillMessageMap = Record<string, SkillMessage>;
|
||||
|
||||
function setSkillMessage(state: SkillsState, key: string, message?: SkillMessage) {
|
||||
if (!key.trim()) return;
|
||||
const next = { ...state.skillMessages };
|
||||
if (message) next[key] = message;
|
||||
else delete next[key];
|
||||
state.skillMessages = next;
|
||||
}
|
||||
|
||||
export async function loadSkills(state: SkillsState) {
|
||||
if (!state.client || !state.connected) return;
|
||||
if (state.skillsLoading) return;
|
||||
@@ -47,8 +63,16 @@ export async function updateSkillEnabled(
|
||||
try {
|
||||
await state.client.request("skills.update", { skillKey, enabled });
|
||||
await loadSkills(state);
|
||||
setSkillMessage(state, skillKey, {
|
||||
kind: "success",
|
||||
message: enabled ? "Skill enabled" : "Skill disabled",
|
||||
});
|
||||
} catch (err) {
|
||||
state.skillsError = String(err);
|
||||
setSkillMessage(state, skillKey, {
|
||||
kind: "error",
|
||||
message: String(err),
|
||||
});
|
||||
} finally {
|
||||
state.skillsBusyKey = null;
|
||||
}
|
||||
@@ -62,8 +86,16 @@ export async function saveSkillApiKey(state: SkillsState, skillKey: string) {
|
||||
const apiKey = state.skillEdits[skillKey] ?? "";
|
||||
await state.client.request("skills.update", { skillKey, apiKey });
|
||||
await loadSkills(state);
|
||||
setSkillMessage(state, skillKey, {
|
||||
kind: "success",
|
||||
message: "API key saved",
|
||||
});
|
||||
} catch (err) {
|
||||
state.skillsError = String(err);
|
||||
setSkillMessage(state, skillKey, {
|
||||
kind: "error",
|
||||
message: String(err),
|
||||
});
|
||||
} finally {
|
||||
state.skillsBusyKey = null;
|
||||
}
|
||||
@@ -78,16 +110,23 @@ export async function installSkill(
|
||||
state.skillsBusyKey = name;
|
||||
state.skillsError = null;
|
||||
try {
|
||||
await state.client.request("skills.install", {
|
||||
const result = (await state.client.request("skills.install", {
|
||||
name,
|
||||
installId,
|
||||
timeoutMs: 120000,
|
||||
});
|
||||
})) as { ok?: boolean; message?: string };
|
||||
await loadSkills(state);
|
||||
setSkillMessage(state, name, {
|
||||
kind: "success",
|
||||
message: result?.message ?? "Installed",
|
||||
});
|
||||
} catch (err) {
|
||||
state.skillsError = String(err);
|
||||
setSkillMessage(state, name, {
|
||||
kind: "error",
|
||||
message: String(err),
|
||||
});
|
||||
} finally {
|
||||
state.skillsBusyKey = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { html, nothing } from "lit";
|
||||
|
||||
import { clampText } from "../format";
|
||||
import type { SkillStatusEntry, SkillStatusReport } from "../types";
|
||||
import type { SkillMessageMap } from "../controllers/skills";
|
||||
|
||||
export type SkillsProps = {
|
||||
loading: boolean;
|
||||
@@ -10,6 +11,7 @@ export type SkillsProps = {
|
||||
filter: string;
|
||||
edits: Record<string, string>;
|
||||
busyKey: string | null;
|
||||
messages: SkillMessageMap;
|
||||
onFilterChange: (next: string) => void;
|
||||
onRefresh: () => void;
|
||||
onToggle: (skillKey: string, enabled: boolean) => void;
|
||||
@@ -73,6 +75,10 @@ export function renderSkills(props: SkillsProps) {
|
||||
function renderSkill(skill: SkillStatusEntry, props: SkillsProps) {
|
||||
const busy = props.busyKey === skill.skillKey || props.busyKey === skill.name;
|
||||
const apiKey = props.edits[skill.skillKey] ?? "";
|
||||
const message =
|
||||
props.messages[skill.skillKey] ?? props.messages[skill.name] ?? null;
|
||||
const canInstall =
|
||||
skill.install.length > 0 && skill.missing.bins.length > 0;
|
||||
const missing = [
|
||||
...skill.missing.bins.map((b) => `bin:${b}`),
|
||||
...skill.missing.env.map((e) => `env:${e}`),
|
||||
@@ -120,16 +126,28 @@ function renderSkill(skill: SkillStatusEntry, props: SkillsProps) {
|
||||
>
|
||||
${skill.disabled ? "Enable" : "Disable"}
|
||||
</button>
|
||||
${skill.install.length > 0
|
||||
${canInstall
|
||||
? html`<button
|
||||
class="btn"
|
||||
?disabled=${busy}
|
||||
@click=${() => props.onInstall(skill.name, skill.install[0].id)}
|
||||
>
|
||||
${skill.install[0].label}
|
||||
${busy ? "Installing…" : skill.install[0].label}
|
||||
</button>`
|
||||
: nothing}
|
||||
</div>
|
||||
${message
|
||||
? html`<div
|
||||
class="muted"
|
||||
style="margin-top: 8px; color: ${
|
||||
message.kind === "error"
|
||||
? "var(--danger-color, #d14343)"
|
||||
: "var(--success-color, #0a7f5a)"
|
||||
};"
|
||||
>
|
||||
${message.message}
|
||||
</div>`
|
||||
: nothing}
|
||||
${skill.primaryEnv
|
||||
? html`
|
||||
<div class="field" style="margin-top: 10px;">
|
||||
|
||||
Reference in New Issue
Block a user