import {
Action,
ActionPanel,
Alert,
Form,
Icon,
List,
Toast,
confirmAlert,
openExtensionPreferences,
showToast,
} from "@vicinae/api";
import { useCallback, useEffect, useState } from "react";
import { usePreferences } from "./lib/hooks/usePreferences";
import {
deleteModel,
describePullProgress,
isSelfHosted,
listModels,
listRunningModels,
pullModel,
unloadModel,
type RunningModel,
} from "./lib/ollama";
import { clearModelSelection, selectModel, type ResolvedPreferences } from "./lib/preferences";
import type { ModelInfo } from "./lib/types";
import { errorMessage, formatBytes } from "./lib/utils";
/**
* Browse, pick, and manage the models the configured Ollama host can reach.
*
* Four jobs in one command, because they are four answers to the same question
* ("what models do I have?"): pick one, unload one, delete one, pull a new one.
* Unloading is the odd one out and the reason a row needs two states rather than
* one: a model can be installed without being loaded, and only the loaded ones
* are holding memory. Delete and pull only appear on a host the user runs, since
* there is nothing to pull onto Ollama Cloud and nothing to delete from it.
*
* ## Where a pick is stored
*
* Not in the `Model` preference, because an extension cannot write its own
* preferences. Vicinae reads them once at startup and holds them in memory, so
* even writing its config file directly does nothing until it restarts, which was
* measured rather than assumed (`providers["@adrianbonpin/lam-agent"].preferences`
* in `~/.config/vicinae/settings.json`, edited while a command was then launched:
* the requests still went to the old host).
*
* So a pick goes to LocalStorage and `loadPreferences` layers it over the
* preference. The cost of that arrangement is one thing this screen has to say
* out loud, because it is invisible otherwise: while a pick is active, editing
* the `Model` preference has no effect until the pick is reset. Which is why the
* setup screen shows both values and offers the reset.
*
* ## Why the pull keeps the window open
*
* The download runs inside this command's worker, so closing the launcher cancels
* it. That is a real constraint and the command description says so.
*/
/** Which surface the command is showing. The form is a view rather than a dialog, which Vicinae has no equivalent of. */
type View = "list" | "pull";
export default function ModelsCommand() {
const { preferences, reload } = usePreferences();
// Nothing renders until the model in use is known, because the whole list is
// marked up relative to it: which row is current, and whether picking one
// would change anything.
if (!preferences) {
return
;
}
return ;
}
interface ModelsScreenProps {
preferences: ResolvedPreferences;
/** Re-reads preferences after this screen changes the model selection. */
reloadPreferences: () => void;
}
function ModelsScreen({ preferences, reloadPreferences }: ModelsScreenProps) {
const [models, setModels] = useState(undefined);
/** By name, so a row can ask whether it is the one holding memory. */
const [running, setRunning] = useState