feat: auto-delete old conversations, and stop a tool call that repeats (#15)
This commit was merged in pull request #15.
This commit is contained in:
+124
-3
@@ -1,6 +1,13 @@
|
||||
import { Action, ActionPanel, Icon, List, openExtensionPreferences } from "@vicinae/api";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { describeResumeTimeout, getLamPreferences, needsSetup } from "./lib/preferences";
|
||||
import { Action, ActionPanel, Alert, Icon, List, confirmAlert, openExtensionPreferences } from "@vicinae/api";
|
||||
import { useCallback, useEffect, useMemo, useState, type ReactNode } from "react";
|
||||
import { clearAllChats, clearOrphanedSnips, runAutoDelete } from "./lib/cleanup";
|
||||
import {
|
||||
describeAutoDelete,
|
||||
describeMaxToolRounds,
|
||||
describeResumeTimeout,
|
||||
getLamPreferences,
|
||||
needsSetup,
|
||||
} from "./lib/preferences";
|
||||
import { listModels } from "./lib/ollama";
|
||||
import { forgetGeoPlace } from "./lib/geo";
|
||||
import { forgetVisionSupport, visionSupport, type VisionSupport } from "./lib/vision";
|
||||
@@ -81,10 +88,58 @@ export default function SetupCommand() {
|
||||
if (!missingKey) void testConnection();
|
||||
}, [missingKey, testConnection]);
|
||||
|
||||
// Opening any Lam command is a moment to collect expired conversations, and
|
||||
// this is one of them. Nothing is open here, so nothing is exempt.
|
||||
useEffect(() => {
|
||||
void runAutoDelete({ maxAgeMs: preferences.autoDeleteMs });
|
||||
}, [preferences.autoDeleteMs]);
|
||||
|
||||
const openPreferences = useCallback(() => {
|
||||
void openExtensionPreferences();
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Confirms, then runs a destructive tidy-up.
|
||||
*
|
||||
* Both actions on this screen delete things that cannot be recovered: there is
|
||||
* no trash to undo them from, and a conversation's screenshots go with it. That
|
||||
* is worth one extra keystroke, and the alert says what will go rather than
|
||||
* asking "are you sure" about nothing in particular.
|
||||
*/
|
||||
const confirmThen = useCallback(
|
||||
async (options: { title: string; message: string }, run: () => Promise<void>) => {
|
||||
const confirmed = await confirmAlert({
|
||||
title: options.title,
|
||||
message: options.message,
|
||||
primaryAction: { title: "Delete", style: Alert.ActionStyle.Destructive },
|
||||
});
|
||||
|
||||
if (confirmed) await run();
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
/**
|
||||
* The action panel for a maintenance row.
|
||||
*
|
||||
* The row's own action comes first, because it is why the user is looking at
|
||||
* the row, and the two read-only actions sit in their own section below so they
|
||||
* can never be the default that Return fires.
|
||||
*/
|
||||
const maintenanceActions = (primary: ReactNode) => (
|
||||
<ActionPanel>
|
||||
{primary}
|
||||
<ActionPanel.Section>
|
||||
<Action title="Open Extension Preferences" icon={Icon.BulletPoints} onAction={openPreferences} />
|
||||
<Action
|
||||
title="Test Connection"
|
||||
icon={Icon.ArrowClockwise}
|
||||
onAction={() => void testConnection()}
|
||||
/>
|
||||
</ActionPanel.Section>
|
||||
</ActionPanel>
|
||||
);
|
||||
|
||||
const settingsActions = (
|
||||
<ActionPanel>
|
||||
<Action title="Open Extension Preferences" icon={Icon.BulletPoints} onAction={openPreferences} />
|
||||
@@ -169,6 +224,18 @@ export default function SetupCommand() {
|
||||
subtitle={describeResumeTimeout(preferences.resumeTimeoutMs)}
|
||||
actions={settingsActions}
|
||||
/>
|
||||
<List.Item
|
||||
icon={Icon.Trash}
|
||||
title="Delete Old Chats After"
|
||||
subtitle={describeAutoDelete(preferences.autoDeleteMs)}
|
||||
actions={settingsActions}
|
||||
/>
|
||||
<List.Item
|
||||
icon={Icon.BulletPoints}
|
||||
title="Tool Call Limit"
|
||||
subtitle={describeMaxToolRounds(preferences.maxToolRounds)}
|
||||
actions={settingsActions}
|
||||
/>
|
||||
<List.Item
|
||||
icon={vision === "yes" ? Icon.Image : Icon.QuestionMarkCircle}
|
||||
title="Image Input"
|
||||
@@ -195,6 +262,60 @@ export default function SetupCommand() {
|
||||
/>
|
||||
</List.Section>
|
||||
|
||||
{/*
|
||||
* Maintenance sits below the settings it acts on. Both rows are destructive
|
||||
* rather than settings, so each builds its own action panel instead of
|
||||
* sharing the one above: these are the only two things on this screen that
|
||||
* delete anything, and neither should be one keystroke away from a row the
|
||||
* user was only reading.
|
||||
*/}
|
||||
<List.Section title="Maintenance">
|
||||
<List.Item
|
||||
icon={Icon.Image}
|
||||
title="Clear Out Unattached Photos"
|
||||
subtitle="Delete captures no conversation refers to any more. Screenshots still attached to a chat are kept, and so is one snipped in the last day."
|
||||
actions={maintenanceActions(
|
||||
<Action
|
||||
title="Clear Unattached Photos"
|
||||
icon={Icon.Image}
|
||||
style="destructive"
|
||||
onAction={() =>
|
||||
void confirmThen(
|
||||
{
|
||||
title: "Delete unattached photos?",
|
||||
message:
|
||||
"Screenshots that belong to no conversation will be deleted. Photos still attached to a chat are kept.",
|
||||
},
|
||||
clearOrphanedSnips,
|
||||
)
|
||||
}
|
||||
/>,
|
||||
)}
|
||||
/>
|
||||
<List.Item
|
||||
icon={Icon.Trash}
|
||||
title="Clear Out All Chats"
|
||||
subtitle="Delete every conversation, and every screenshot it captured. This cannot be undone."
|
||||
actions={maintenanceActions(
|
||||
<Action
|
||||
title="Clear All Chats"
|
||||
icon={Icon.Trash}
|
||||
style="destructive"
|
||||
onAction={() =>
|
||||
void confirmThen(
|
||||
{
|
||||
title: "Delete every conversation?",
|
||||
message:
|
||||
"Every conversation and every screenshot it captured will be deleted. This cannot be undone.",
|
||||
},
|
||||
clearAllChats,
|
||||
)
|
||||
}
|
||||
/>,
|
||||
)}
|
||||
/>
|
||||
</List.Section>
|
||||
|
||||
{error ? (
|
||||
<List.Section title="Last Error">
|
||||
<List.Item
|
||||
|
||||
Reference in New Issue
Block a user