refactor: normalize skills UI status keys

This commit is contained in:
Peter Steinberger
2026-01-08 20:13:21 +00:00
parent 5a2242ee92
commit 0c74ef25d6
3 changed files with 35 additions and 18 deletions

View File

@@ -396,11 +396,12 @@ export function renderApp(state: AppViewState) {
messages: state.skillMessages, 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, { clearMessages: true }),
onToggle: (key, enabled) => updateSkillEnabled(state, key, enabled), onToggle: (key, enabled) => updateSkillEnabled(state, key, enabled),
onEdit: (key, value) => updateSkillEdit(state, key, value), onEdit: (key, value) => updateSkillEdit(state, key, value),
onSaveKey: (key) => saveSkillApiKey(state, key), onSaveKey: (key) => saveSkillApiKey(state, key),
onInstall: (name, installId) => installSkill(state, name, installId), onInstall: (skillKey, name, installId) =>
installSkill(state, skillKey, name, installId),
}) })
: nothing} : nothing}

View File

@@ -19,6 +19,10 @@ export type SkillMessage = {
export type SkillMessageMap = Record<string, SkillMessage>; export type SkillMessageMap = Record<string, SkillMessage>;
type LoadSkillsOptions = {
clearMessages?: boolean;
};
function setSkillMessage(state: SkillsState, key: string, message?: SkillMessage) { function setSkillMessage(state: SkillsState, key: string, message?: SkillMessage) {
if (!key.trim()) return; if (!key.trim()) return;
const next = { ...state.skillMessages }; const next = { ...state.skillMessages };
@@ -27,7 +31,15 @@ function setSkillMessage(state: SkillsState, key: string, message?: SkillMessage
state.skillMessages = next; state.skillMessages = next;
} }
export async function loadSkills(state: SkillsState) { function getErrorMessage(err: unknown) {
if (err instanceof Error) return err.message;
return String(err);
}
export async function loadSkills(state: SkillsState, options?: LoadSkillsOptions) {
if (options?.clearMessages && Object.keys(state.skillMessages).length > 0) {
state.skillMessages = {};
}
if (!state.client || !state.connected) return; if (!state.client || !state.connected) return;
if (state.skillsLoading) return; if (state.skillsLoading) return;
state.skillsLoading = true; state.skillsLoading = true;
@@ -38,7 +50,7 @@ export async function loadSkills(state: SkillsState) {
| undefined; | undefined;
if (res) state.skillsReport = res; if (res) state.skillsReport = res;
} catch (err) { } catch (err) {
state.skillsError = String(err); state.skillsError = getErrorMessage(err);
} finally { } finally {
state.skillsLoading = false; state.skillsLoading = false;
} }
@@ -68,10 +80,11 @@ export async function updateSkillEnabled(
message: enabled ? "Skill enabled" : "Skill disabled", message: enabled ? "Skill enabled" : "Skill disabled",
}); });
} catch (err) { } catch (err) {
state.skillsError = String(err); const message = getErrorMessage(err);
state.skillsError = message;
setSkillMessage(state, skillKey, { setSkillMessage(state, skillKey, {
kind: "error", kind: "error",
message: String(err), message,
}); });
} finally { } finally {
state.skillsBusyKey = null; state.skillsBusyKey = null;
@@ -91,10 +104,11 @@ export async function saveSkillApiKey(state: SkillsState, skillKey: string) {
message: "API key saved", message: "API key saved",
}); });
} catch (err) { } catch (err) {
state.skillsError = String(err); const message = getErrorMessage(err);
state.skillsError = message;
setSkillMessage(state, skillKey, { setSkillMessage(state, skillKey, {
kind: "error", kind: "error",
message: String(err), message,
}); });
} finally { } finally {
state.skillsBusyKey = null; state.skillsBusyKey = null;
@@ -103,11 +117,12 @@ export async function saveSkillApiKey(state: SkillsState, skillKey: string) {
export async function installSkill( export async function installSkill(
state: SkillsState, state: SkillsState,
skillKey: string,
name: string, name: string,
installId: string, installId: string,
) { ) {
if (!state.client || !state.connected) return; if (!state.client || !state.connected) return;
state.skillsBusyKey = name; state.skillsBusyKey = skillKey;
state.skillsError = null; state.skillsError = null;
try { try {
const result = (await state.client.request("skills.install", { const result = (await state.client.request("skills.install", {
@@ -116,15 +131,16 @@ export async function installSkill(
timeoutMs: 120000, timeoutMs: 120000,
})) as { ok?: boolean; message?: string }; })) as { ok?: boolean; message?: string };
await loadSkills(state); await loadSkills(state);
setSkillMessage(state, name, { setSkillMessage(state, skillKey, {
kind: "success", kind: "success",
message: result?.message ?? "Installed", message: result?.message ?? "Installed",
}); });
} catch (err) { } catch (err) {
state.skillsError = String(err); const message = getErrorMessage(err);
setSkillMessage(state, name, { state.skillsError = message;
setSkillMessage(state, skillKey, {
kind: "error", kind: "error",
message: String(err), message,
}); });
} finally { } finally {
state.skillsBusyKey = null; state.skillsBusyKey = null;

View File

@@ -17,7 +17,7 @@ export type SkillsProps = {
onToggle: (skillKey: string, enabled: boolean) => void; onToggle: (skillKey: string, enabled: boolean) => void;
onEdit: (skillKey: string, value: string) => void; onEdit: (skillKey: string, value: string) => void;
onSaveKey: (skillKey: string) => void; onSaveKey: (skillKey: string) => void;
onInstall: (name: string, installId: string) => void; onInstall: (skillKey: string, name: string, installId: string) => void;
}; };
export function renderSkills(props: SkillsProps) { export function renderSkills(props: SkillsProps) {
@@ -73,10 +73,9 @@ 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;
const apiKey = props.edits[skill.skillKey] ?? ""; const apiKey = props.edits[skill.skillKey] ?? "";
const message = const message = props.messages[skill.skillKey] ?? null;
props.messages[skill.skillKey] ?? props.messages[skill.name] ?? null;
const canInstall = const canInstall =
skill.install.length > 0 && skill.missing.bins.length > 0; skill.install.length > 0 && skill.missing.bins.length > 0;
const missing = [ const missing = [
@@ -130,7 +129,8 @@ function renderSkill(skill: SkillStatusEntry, props: SkillsProps) {
? 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.skillKey, skill.name, skill.install[0].id)}
> >
${busy ? "Installing…" : skill.install[0].label} ${busy ? "Installing…" : skill.install[0].label}
</button>` </button>`