feat(ui): delete sessions from Control UI
This commit is contained in:
parent
76d3d58b5c
commit
929b86e302
@ -27,6 +27,7 @@
|
|||||||
- TUI: show provider/model labels for the active session and default model.
|
- TUI: show provider/model labels for the active session and default model.
|
||||||
- Heartbeat: add per-agent heartbeat configuration and multi-agent docs example.
|
- Heartbeat: add per-agent heartbeat configuration and multi-agent docs example.
|
||||||
- UI: show gateway auth guidance + doc link on unauthorized Control UI connections.
|
- UI: show gateway auth guidance + doc link on unauthorized Control UI connections.
|
||||||
|
- UI: add session deletion action in Control UI sessions list. (#1017) — thanks @Szpadel.
|
||||||
- Security: warn on weak model tiers (Haiku, below GPT-5, below Claude 4.5) in `clawdbot security audit`.
|
- Security: warn on weak model tiers (Haiku, below GPT-5, below Claude 4.5) in `clawdbot security audit`.
|
||||||
- Apps: store node auth tokens encrypted (Keychain/SecurePrefs).
|
- Apps: store node auth tokens encrypted (Keychain/SecurePrefs).
|
||||||
- Cron: isolated cron jobs now start a fresh session id on every run to prevent context buildup.
|
- Cron: isolated cron jobs now start a fresh session id on every run to prevent context buildup.
|
||||||
|
|||||||
@ -537,7 +537,7 @@
|
|||||||
.table-head,
|
.table-head,
|
||||||
.table-row {
|
.table-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1.4fr 0.8fr 0.8fr 0.7fr 0.8fr 0.8fr;
|
grid-template-columns: 1.4fr 1fr 0.8fr 0.7fr 0.8fr 0.8fr 0.8fr 0.8fr 0.6fr;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,7 +57,7 @@ import {
|
|||||||
updateTelegramForm,
|
updateTelegramForm,
|
||||||
} from "./controllers/connections";
|
} from "./controllers/connections";
|
||||||
import { loadPresence } from "./controllers/presence";
|
import { loadPresence } from "./controllers/presence";
|
||||||
import { loadSessions, patchSession } from "./controllers/sessions";
|
import { deleteSession, loadSessions, patchSession } from "./controllers/sessions";
|
||||||
import {
|
import {
|
||||||
installSkill,
|
installSkill,
|
||||||
loadSkills,
|
loadSkills,
|
||||||
@ -277,11 +277,12 @@ export function renderApp(state: AppViewState) {
|
|||||||
state.sessionsFilterLimit = next.limit;
|
state.sessionsFilterLimit = next.limit;
|
||||||
state.sessionsIncludeGlobal = next.includeGlobal;
|
state.sessionsIncludeGlobal = next.includeGlobal;
|
||||||
state.sessionsIncludeUnknown = next.includeUnknown;
|
state.sessionsIncludeUnknown = next.includeUnknown;
|
||||||
},
|
},
|
||||||
onRefresh: () => loadSessions(state),
|
onRefresh: () => loadSessions(state),
|
||||||
onPatch: (key, patch) => patchSession(state, key, patch),
|
onPatch: (key, patch) => patchSession(state, key, patch),
|
||||||
})
|
onDelete: (key) => deleteSession(state, key),
|
||||||
: nothing}
|
})
|
||||||
|
: nothing}
|
||||||
|
|
||||||
${state.tab === "cron"
|
${state.tab === "cron"
|
||||||
? renderCron({
|
? renderCron({
|
||||||
|
|||||||
@ -60,3 +60,22 @@ export async function patchSession(
|
|||||||
state.sessionsError = String(err);
|
state.sessionsError = String(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteSession(state: SessionsState, key: string) {
|
||||||
|
if (!state.client || !state.connected) return;
|
||||||
|
if (state.sessionsLoading) return;
|
||||||
|
const confirmed = window.confirm(
|
||||||
|
`Delete session "${key}"?\n\nDeletes the session entry and archives its transcript.`,
|
||||||
|
);
|
||||||
|
if (!confirmed) return;
|
||||||
|
state.sessionsLoading = true;
|
||||||
|
state.sessionsError = null;
|
||||||
|
try {
|
||||||
|
await state.client.request("sessions.delete", { key, deleteTranscript: true });
|
||||||
|
await loadSessions(state);
|
||||||
|
} catch (err) {
|
||||||
|
state.sessionsError = String(err);
|
||||||
|
} finally {
|
||||||
|
state.sessionsLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -29,6 +29,7 @@ export type SessionsProps = {
|
|||||||
reasoningLevel?: string | null;
|
reasoningLevel?: string | null;
|
||||||
},
|
},
|
||||||
) => void;
|
) => void;
|
||||||
|
onDelete: (key: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const THINK_LEVELS = ["", "off", "minimal", "low", "medium", "high"] as const;
|
const THINK_LEVELS = ["", "off", "minimal", "low", "medium", "high"] as const;
|
||||||
@ -157,10 +158,13 @@ export function renderSessions(props: SessionsProps) {
|
|||||||
<div>Thinking</div>
|
<div>Thinking</div>
|
||||||
<div>Verbose</div>
|
<div>Verbose</div>
|
||||||
<div>Reasoning</div>
|
<div>Reasoning</div>
|
||||||
|
<div>Actions</div>
|
||||||
</div>
|
</div>
|
||||||
${rows.length === 0
|
${rows.length === 0
|
||||||
? html`<div class="muted">No sessions found.</div>`
|
? html`<div class="muted">No sessions found.</div>`
|
||||||
: rows.map((row) => renderRow(row, props.basePath, props.onPatch))}
|
: rows.map((row) =>
|
||||||
|
renderRow(row, props.basePath, props.onPatch, props.onDelete, props.loading),
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
`;
|
`;
|
||||||
@ -170,6 +174,8 @@ function renderRow(
|
|||||||
row: GatewaySessionRow,
|
row: GatewaySessionRow,
|
||||||
basePath: string,
|
basePath: string,
|
||||||
onPatch: SessionsProps["onPatch"],
|
onPatch: SessionsProps["onPatch"],
|
||||||
|
onDelete: SessionsProps["onDelete"],
|
||||||
|
disabled: boolean,
|
||||||
) {
|
) {
|
||||||
const updated = row.updatedAt ? formatAgo(row.updatedAt) : "n/a";
|
const updated = row.updatedAt ? formatAgo(row.updatedAt) : "n/a";
|
||||||
const rawThinking = row.thinkingLevel ?? "";
|
const rawThinking = row.thinkingLevel ?? "";
|
||||||
@ -196,6 +202,7 @@ function renderRow(
|
|||||||
<div>
|
<div>
|
||||||
<select
|
<select
|
||||||
.value=${thinking}
|
.value=${thinking}
|
||||||
|
?disabled=${disabled}
|
||||||
@change=${(e: Event) => {
|
@change=${(e: Event) => {
|
||||||
const value = (e.target as HTMLSelectElement).value;
|
const value = (e.target as HTMLSelectElement).value;
|
||||||
onPatch(row.key, {
|
onPatch(row.key, {
|
||||||
@ -211,6 +218,7 @@ function renderRow(
|
|||||||
<div>
|
<div>
|
||||||
<select
|
<select
|
||||||
.value=${verbose}
|
.value=${verbose}
|
||||||
|
?disabled=${disabled}
|
||||||
@change=${(e: Event) => {
|
@change=${(e: Event) => {
|
||||||
const value = (e.target as HTMLSelectElement).value;
|
const value = (e.target as HTMLSelectElement).value;
|
||||||
onPatch(row.key, { verboseLevel: value || null });
|
onPatch(row.key, { verboseLevel: value || null });
|
||||||
@ -224,6 +232,7 @@ function renderRow(
|
|||||||
<div>
|
<div>
|
||||||
<select
|
<select
|
||||||
.value=${reasoning}
|
.value=${reasoning}
|
||||||
|
?disabled=${disabled}
|
||||||
@change=${(e: Event) => {
|
@change=${(e: Event) => {
|
||||||
const value = (e.target as HTMLSelectElement).value;
|
const value = (e.target as HTMLSelectElement).value;
|
||||||
onPatch(row.key, { reasoningLevel: value || null });
|
onPatch(row.key, { reasoningLevel: value || null });
|
||||||
@ -234,6 +243,11 @@ function renderRow(
|
|||||||
)}
|
)}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<button class="btn danger" ?disabled=${disabled} @click=${() => onDelete(row.key)}>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user