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