Archived
Add model and thinking controls
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
export { api, filesApi, gitApi, projectsApi, sessionsApi, terminalsApi, workspacesApi } from "./api/clients";
|
||||
export { globalSessionEvents, sessionEvents, terminalSocket } from "./api/sockets";
|
||||
export type { CommandOption, CommandResult, FileContentResponse, FileSuggestion, FileTreeEntry, FileTreeResponse, GitDiffResponse, GitFileState, GitStatusFile, GitStatusResponse, MessagePage, Project, QueuedSessionMessage, SessionActivity, SessionInfo, SessionStatus, SlashCommand, SessionUiEvent, TerminalInfo, Workspace } from "../../shared/apiTypes";
|
||||
export type { CommandOption, CommandResult, FileContentResponse, FileSuggestion, FileTreeEntry, FileTreeResponse, GitDiffResponse, GitFileState, GitStatusFile, GitStatusResponse, MessagePage, ModelSelectionResponse, Project, QueuedSessionMessage, SessionActivity, SessionInfo, SessionModel, SessionStatus, SlashCommand, SessionUiEvent, TerminalInfo, ThinkingLevel, ThinkingLevelsResponse, Workspace } from "../../shared/apiTypes";
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
parseGitDiffResponse,
|
||||
parseGitStatusResponse,
|
||||
parseMessagePage,
|
||||
parseModelSelectionResponse,
|
||||
parseProject,
|
||||
parseRestored,
|
||||
parseSessionInfo,
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
parseSlashCommand,
|
||||
parseStopped,
|
||||
parseTerminalInfo,
|
||||
parseThinkingLevelsResponse,
|
||||
parseWorkspace,
|
||||
} from "./parsers";
|
||||
import { gitDiffUrl, messageUrl } from "./urls";
|
||||
@@ -43,6 +45,12 @@ export const sessionsApi = {
|
||||
startSession: (cwd: string) => request("/api/sessions", parseSessionInfo, { method: "POST", body: JSON.stringify({ cwd }) }),
|
||||
messages: (sessionId: string, options?: { limit?: number; before?: number }) => request(messageUrl(sessionId, options), parseMessagePage),
|
||||
status: (sessionId: string) => request(`/api/sessions/${sessionId}/status`, parseSessionStatus),
|
||||
models: (sessionId: string) => request(`/api/sessions/${sessionId}/models`, parseModelSelectionResponse),
|
||||
setModel: (sessionId: string, provider: string, modelId: string) => request(`/api/sessions/${sessionId}/model`, parseSessionStatus, { method: "POST", body: JSON.stringify({ provider, modelId }) }),
|
||||
cycleModel: (sessionId: string, direction: "forward" | "backward") => request(`/api/sessions/${sessionId}/model/cycle`, parseSessionStatus, { method: "POST", body: JSON.stringify({ direction }) }),
|
||||
thinkingLevels: (sessionId: string) => request(`/api/sessions/${sessionId}/thinking-levels`, parseThinkingLevelsResponse),
|
||||
setThinkingLevel: (sessionId: string, level: "off" | "minimal" | "low" | "medium" | "high" | "xhigh") => request(`/api/sessions/${sessionId}/thinking-level`, parseSessionStatus, { method: "POST", body: JSON.stringify({ level }) }),
|
||||
cycleThinkingLevel: (sessionId: string) => request(`/api/sessions/${sessionId}/thinking-level/cycle`, parseSessionStatus, { method: "POST" }),
|
||||
commands: (sessionId: string) => request(`/api/sessions/${sessionId}/commands`, arrayOf(parseSlashCommand)),
|
||||
prompt: (sessionId: string, text: string, streamingBehavior?: "steer" | "followUp") => request(`/api/sessions/${sessionId}/prompt`, parseAccepted, { method: "POST", body: JSON.stringify(streamingBehavior === undefined ? { text } : { text, streamingBehavior }) }),
|
||||
shell: (sessionId: string, text: string) => request(`/api/sessions/${sessionId}/shell`, parseAccepted, { method: "POST", body: JSON.stringify({ text }) }),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CommandOption, CommandResult, FileContentResponse, FileSuggestion, FileTreeEntry, FileTreeResponse, GitDiffResponse, GitFileState, GitStatusFile, GitStatusResponse, MessagePage, Project, QueuedSessionMessage, SessionInfo, SessionStatus, SlashCommand, TerminalInfo, Workspace } from "../../../shared/apiTypes";
|
||||
import type { CommandOption, CommandResult, FileContentResponse, FileSuggestion, FileTreeEntry, FileTreeResponse, GitDiffResponse, GitFileState, GitStatusFile, GitStatusResponse, MessagePage, ModelSelectionResponse, Project, QueuedSessionMessage, SessionInfo, SessionModel, SessionStatus, SlashCommand, TerminalInfo, ThinkingLevel, ThinkingLevelsResponse, Workspace } from "../../../shared/apiTypes";
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null;
|
||||
@@ -127,10 +127,29 @@ function parseTokens(value: unknown): SessionStatus["tokens"] {
|
||||
};
|
||||
}
|
||||
|
||||
function parseSessionModel(value: unknown): SessionModel {
|
||||
const record = requireRecord(value);
|
||||
return { ...optionalField("provider", optionalString(record, "provider")), ...optionalField("id", optionalString(record, "id")), ...optionalField("name", optionalString(record, "name")), ...optionalField("contextWindow", optionalNumber(record, "contextWindow")), ...optionalField("reasoning", record["reasoning"]) };
|
||||
}
|
||||
|
||||
function optionalModel(value: unknown): Pick<SessionStatus, "model"> | object {
|
||||
if (value === undefined) return {};
|
||||
return { model: parseSessionModel(value) };
|
||||
}
|
||||
|
||||
export function parseModelSelectionResponse(value: unknown): ModelSelectionResponse {
|
||||
const record = requireRecord(value);
|
||||
return { model: { ...optionalField("provider", optionalString(record, "provider")), ...optionalField("id", optionalString(record, "id")), ...optionalField("name", optionalString(record, "name")), ...optionalField("contextWindow", optionalNumber(record, "contextWindow")), ...optionalField("reasoning", record["reasoning"]) } };
|
||||
return { models: arrayOf(parseSessionModel)(record["models"]) };
|
||||
}
|
||||
|
||||
function parseThinkingLevel(value: unknown): ThinkingLevel {
|
||||
if (value !== "off" && value !== "minimal" && value !== "low" && value !== "medium" && value !== "high" && value !== "xhigh") throw new Error("Invalid thinking level");
|
||||
return value;
|
||||
}
|
||||
|
||||
export function parseThinkingLevelsResponse(value: unknown): ThinkingLevelsResponse {
|
||||
const record = requireRecord(value);
|
||||
return { levels: arrayOf(parseThinkingLevel)(record["levels"]) };
|
||||
}
|
||||
|
||||
function optionalContextUsage(value: unknown): Pick<SessionStatus, "contextUsage"> | object {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CommandResult, FileContentResponse, FileTreeEntry, GitDiffResponse, GitStatusResponse, Project, SessionActivity, SessionInfo, SessionStatus, Workspace } from "./api";
|
||||
import type { CommandOption, CommandResult, FileContentResponse, FileTreeEntry, GitDiffResponse, GitStatusResponse, Project, SessionActivity, SessionInfo, SessionStatus, Workspace } from "./api";
|
||||
import type { ChatLine } from "./components/shared";
|
||||
import type { QualifiedContributionId } from "./plugins/types";
|
||||
|
||||
@@ -19,6 +19,8 @@ export interface AppState {
|
||||
sessionStatuses: Record<string, SessionStatus>;
|
||||
sessionActivities: Record<string, SessionActivity>;
|
||||
commandDialog: Extract<CommandResult, { type: "select" }> | undefined;
|
||||
modelDialog: { title: string; options: CommandOption[]; selectedValue?: string } | undefined;
|
||||
thinkingDialog: { title: string; options: CommandOption[]; selectedValue?: string } | undefined;
|
||||
actionPaletteOpen: boolean;
|
||||
projectDialogOpen: boolean;
|
||||
workspaceTool: QualifiedContributionId;
|
||||
@@ -54,6 +56,8 @@ export function initialAppState(): AppState {
|
||||
sessionStatuses: {},
|
||||
sessionActivities: {},
|
||||
commandDialog: undefined,
|
||||
modelDialog: undefined,
|
||||
thinkingDialog: undefined,
|
||||
actionPaletteOpen: false,
|
||||
projectDialogOpen: false,
|
||||
workspaceTool: "core:workspace.files",
|
||||
|
||||
@@ -7,12 +7,16 @@ import { commandPickerStyles } from "./shared";
|
||||
@customElement("command-picker")
|
||||
export class CommandPicker extends LitElement {
|
||||
@property() override title = "Select";
|
||||
@property({ type: Boolean }) searchable = false;
|
||||
@property({ attribute: false }) options: CommandOption[] = [];
|
||||
@property({ attribute: false }) selectedValue?: string;
|
||||
@property({ attribute: false }) onPick?: (value: string) => void;
|
||||
@property({ attribute: false }) onCancel?: () => void;
|
||||
@state() private selectedIndex = 0;
|
||||
@state() private query = "";
|
||||
|
||||
override render() {
|
||||
const options = this.filteredOptions();
|
||||
return html`
|
||||
<div class="backdrop" @mousedown=${() => this.onCancel?.()}>
|
||||
<section @mousedown=${(event: MouseEvent) => { event.stopPropagation(); }}>
|
||||
@@ -20,13 +24,15 @@ export class CommandPicker extends LitElement {
|
||||
<strong>${this.title}</strong>
|
||||
<button @click=${() => this.onCancel?.()}>×</button>
|
||||
</header>
|
||||
${this.searchable ? html`<input placeholder="Search" .value=${this.query} @input=${(event: Event) => { this.handleSearchInput(event); }} @keydown=${(event: KeyboardEvent) => { this.handleKeyDown(event); }}>` : null}
|
||||
<div class="options" @keydown=${(event: KeyboardEvent) => { this.handleKeyDown(event); }} tabindex="0">
|
||||
${this.options.map((option, index) => html`
|
||||
${options.map((option, index) => html`
|
||||
<button class=${index === this.selectedIndex ? "selected" : ""} ${scrollWhenSelected(index === this.selectedIndex, option.value)} @click=${() => this.onPick?.(option.value)}>
|
||||
<span>${option.label}</span>
|
||||
${option.description !== undefined && option.description !== "" ? html`<small>${option.description}</small>` : null}
|
||||
</button>
|
||||
`)}
|
||||
${options.length === 0 ? html`<div class="empty">No matching options</div>` : null}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -34,22 +40,43 @@ export class CommandPicker extends LitElement {
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
this.renderRoot.querySelector<HTMLElement>(".options")?.focus();
|
||||
this.selectInitialValue();
|
||||
this.renderRoot.querySelector<HTMLElement>(this.searchable ? "input" : ".options")?.focus();
|
||||
}
|
||||
|
||||
private selectInitialValue(): void {
|
||||
if (this.selectedValue === undefined) return;
|
||||
const index = this.filteredOptions().findIndex((option) => option.value === this.selectedValue);
|
||||
if (index >= 0) this.selectedIndex = index;
|
||||
}
|
||||
|
||||
private handleSearchInput(event: Event): void {
|
||||
if (event.target instanceof HTMLInputElement) {
|
||||
this.query = event.target.value;
|
||||
this.selectedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private filteredOptions(): CommandOption[] {
|
||||
const query = this.query.trim().toLowerCase();
|
||||
if (query === "") return this.options;
|
||||
return this.options.filter((option) => `${option.label} ${option.description ?? ""} ${option.value}`.toLowerCase().includes(query));
|
||||
}
|
||||
|
||||
private handleKeyDown(event: KeyboardEvent) {
|
||||
const options = this.filteredOptions();
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
this.onCancel?.();
|
||||
} else if (event.key === "ArrowDown") {
|
||||
event.preventDefault();
|
||||
if (this.options.length > 0) this.selectedIndex = (this.selectedIndex + 1) % this.options.length;
|
||||
if (options.length > 0) this.selectedIndex = (this.selectedIndex + 1) % options.length;
|
||||
} else if (event.key === "ArrowUp") {
|
||||
event.preventDefault();
|
||||
if (this.options.length > 0) this.selectedIndex = (this.selectedIndex - 1 + this.options.length) % this.options.length;
|
||||
if (options.length > 0) this.selectedIndex = (this.selectedIndex - 1 + options.length) % options.length;
|
||||
} else if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
const option = this.options[this.selectedIndex];
|
||||
const option = options[this.selectedIndex];
|
||||
if (option) this.onPick?.(option.value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, query, state } from "lit/decorators.js";
|
||||
import type { Project, SessionInfo, Workspace } from "../api";
|
||||
import type { Project, SessionInfo, ThinkingLevel, Workspace } from "../api";
|
||||
import type { AppAction } from "../actions";
|
||||
import { initialAppState, type AppState } from "../appState";
|
||||
import { FileExplorerController } from "../controllers/fileExplorerController";
|
||||
@@ -246,6 +246,48 @@ export class PiWebApp extends LitElement {
|
||||
if (action !== undefined) void action.run();
|
||||
}
|
||||
|
||||
private async openModelDialog() {
|
||||
const models = await this.sessions.listModels();
|
||||
const currentProvider = this.state.status?.model?.provider;
|
||||
const currentId = this.state.status?.model?.id;
|
||||
this.setState({
|
||||
modelDialog: {
|
||||
title: "Select Model",
|
||||
...(currentProvider !== undefined && currentId !== undefined ? { selectedValue: `${currentProvider}/${currentId}` } : {}),
|
||||
options: models.map((model) => {
|
||||
const provider = model.provider ?? "";
|
||||
const id = model.id ?? "";
|
||||
const isCurrent = provider === currentProvider && id === currentId;
|
||||
return { value: `${provider}/${id}`, label: `${id}${isCurrent ? " ✓ current" : ""}`, description: provider };
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async pickModel(value: string) {
|
||||
this.setState({ modelDialog: undefined });
|
||||
const slash = value.indexOf("/");
|
||||
if (slash <= 0) return;
|
||||
await this.sessions.setModel(value.slice(0, slash), value.slice(slash + 1));
|
||||
}
|
||||
|
||||
private async openThinkingDialog() {
|
||||
const levels = await this.sessions.listThinkingLevels();
|
||||
const current = this.state.status?.thinkingLevel ?? "off";
|
||||
this.setState({
|
||||
thinkingDialog: {
|
||||
title: "Select Thinking Level",
|
||||
selectedValue: current,
|
||||
options: levels.map((level) => ({ value: level, label: `${level}${level === current ? " ✓ current" : ""}`, description: thinkingDescription(level) })),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async pickThinking(value: string) {
|
||||
this.setState({ thinkingDialog: undefined });
|
||||
if (isThinkingLevel(value)) await this.sessions.setThinkingLevel(value);
|
||||
}
|
||||
|
||||
override render() {
|
||||
const state = this.state;
|
||||
return html`
|
||||
@@ -264,8 +306,10 @@ export class PiWebApp extends LitElement {
|
||||
${state.selectedSession ? html`
|
||||
<chat-view .sessionId=${state.selectedSession.id} .messages=${state.messages} .messageStart=${state.messagePageStart} .messageTotal=${state.messagePageTotal} .hasMore=${state.messagePageStart > 0} .loadingMore=${state.isLoadingEarlierMessages} .isReceivingPartialStream=${state.isReceivingPartialStream} .isCompacting=${state.status?.isCompacting === true} .pendingMessageCount=${state.status?.pendingMessageCount ?? 0} .status=${state.status} .activity=${state.activity} .onLoadMore=${() => this.withChatPrependTransition(() => this.sessions.loadEarlierMessages())}></chat-view>
|
||||
<prompt-editor .sessionId=${state.selectedSession.id} .cwd=${state.selectedWorkspace?.path} .disabled=${state.selectedSession.archived === true} .canSteer=${state.status?.isStreaming === true} .isCompacting=${state.status?.isCompacting === true} .canStop=${state.status?.isStreaming === true || state.status?.isBashRunning === true || state.status?.isCompacting === true || (state.status?.pendingMessageCount ?? 0) > 0} .onSend=${(text: string, streamingBehavior?: "steer" | "followUp") => this.sessions.send(text, streamingBehavior)} .onStop=${() => this.sessions.stopActiveWork()}></prompt-editor>
|
||||
<status-bar .status=${state.status} .workspace=${state.selectedWorkspace}></status-bar>
|
||||
<status-bar .status=${state.status} .workspace=${state.selectedWorkspace} .onSelectModel=${() => { void this.openModelDialog(); }} .onCycleModel=${(direction: "forward" | "backward") => { void this.sessions.cycleModel(direction); }} .onSelectThinking=${() => { void this.openThinkingDialog(); }} .onCycleThinking=${() => { void this.sessions.cycleThinkingLevel(); }}></status-bar>
|
||||
${state.commandDialog !== undefined ? html`<command-picker .title=${state.commandDialog.title} .options=${state.commandDialog.options} .onPick=${(value: string) => this.sessions.respondToCommand(state.commandDialog?.requestId ?? "", value)} .onCancel=${() => { this.sessions.cancelCommand(); }}></command-picker>` : null}
|
||||
${state.modelDialog !== undefined ? html`<command-picker title=${state.modelDialog.title} .searchable=${true} .options=${state.modelDialog.options} .selectedValue=${state.modelDialog.selectedValue} .onPick=${(value: string) => { void this.pickModel(value); }} .onCancel=${() => { this.setState({ modelDialog: undefined }); }}></command-picker>` : null}
|
||||
${state.thinkingDialog !== undefined ? html`<command-picker title=${state.thinkingDialog.title} .options=${state.thinkingDialog.options} .selectedValue=${state.thinkingDialog.selectedValue} .onPick=${(value: string) => { void this.pickThinking(value); }} .onCancel=${() => { this.setState({ thinkingDialog: undefined }); }}></command-picker>` : null}
|
||||
` : html`<div class="empty">Select or start a session.</div>`}
|
||||
<div class="mobile-panel">${this.renderWorkspacePanel(true)}</div>
|
||||
</main>
|
||||
@@ -292,3 +336,18 @@ function isActive(status: AppState["status"]): boolean {
|
||||
function nextFrame(): Promise<void> {
|
||||
return new Promise((resolve) => requestAnimationFrame(() => { resolve(); }));
|
||||
}
|
||||
|
||||
function isThinkingLevel(value: string): value is ThinkingLevel {
|
||||
return value === "off" || value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh";
|
||||
}
|
||||
|
||||
function thinkingDescription(level: ThinkingLevel): string {
|
||||
switch (level) {
|
||||
case "off": return "No reasoning";
|
||||
case "minimal": return "Very brief reasoning (~1k tokens)";
|
||||
case "low": return "Light reasoning (~2k tokens)";
|
||||
case "medium": return "Moderate reasoning (~8k tokens)";
|
||||
case "high": return "Deep reasoning (~16k tokens)";
|
||||
case "xhigh": return "Maximum reasoning (~32k tokens)";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@ import { statusBarStyles } from "./shared";
|
||||
export class StatusBar extends LitElement {
|
||||
@property({ attribute: false }) status?: SessionStatus;
|
||||
@property({ attribute: false }) workspace?: Workspace;
|
||||
@property({ attribute: false }) onSelectModel?: () => void;
|
||||
@property({ attribute: false }) onCycleModel?: (direction: "forward" | "backward") => void;
|
||||
@property({ attribute: false }) onSelectThinking?: () => void;
|
||||
@property({ attribute: false }) onCycleThinking?: () => void;
|
||||
|
||||
override render() {
|
||||
const status = this.status;
|
||||
@@ -24,8 +28,15 @@ export class StatusBar extends LitElement {
|
||||
return html`
|
||||
<div class="bar">
|
||||
<span title=${this.workspace?.path ?? ""}>${this.workspace?.label ?? "workspace"}</span>
|
||||
<span>${provider}${model}</span>
|
||||
<span>thinking ${status.thinkingLevel ?? "off"}</span>
|
||||
<span class="control-group">
|
||||
<button title="Previous model" @click=${() => this.onCycleModel?.("backward")}>‹</button>
|
||||
<button title="Select model" @click=${() => this.onSelectModel?.()}>${provider}${model}</button>
|
||||
<button title="Next model" @click=${() => this.onCycleModel?.("forward")}>›</button>
|
||||
</span>
|
||||
<span class="control-group">
|
||||
<button title="Cycle thinking level" @click=${() => this.onCycleThinking?.()}>thinking ${status.thinkingLevel ?? "off"}</button>
|
||||
<button title="Select thinking level" @click=${() => this.onSelectThinking?.()}>⌄</button>
|
||||
</span>
|
||||
<span>↑${formatTokenCount(tokens.input)}</span>
|
||||
<span>↓${formatTokenCount(tokens.output)}</span>
|
||||
<span>${contextText}</span>
|
||||
|
||||
@@ -224,6 +224,9 @@ export const statusBarStyles = css`
|
||||
:host { display: block; color: #8b949e; font: 12px system-ui, sans-serif; }
|
||||
.bar { display: flex; gap: 12px; align-items: center; min-width: 0; padding: 7px 12px; border-top: 1px solid #30363d; background: #0d1117; white-space: nowrap; overflow: hidden; }
|
||||
span { overflow: hidden; text-overflow: ellipsis; }
|
||||
button { border: 0; border-radius: 4px; background: transparent; color: inherit; padding: 1px 3px; font: inherit; cursor: pointer; }
|
||||
button:hover, button:focus { background: #21262d; color: #e6edf3; }
|
||||
.control-group { display: inline-flex; align-items: center; gap: 2px; flex: 0 1 auto; min-width: 0; }
|
||||
.bar > span:first-child { flex: 1 1 auto; min-width: 80px; }
|
||||
.activity { display: inline-flex; align-items: center; gap: 6px; color: #8b949e; }
|
||||
.activity.active { color: #3fb950; }
|
||||
@@ -251,9 +254,12 @@ export const commandPickerStyles = css`
|
||||
.options { min-height: 0; overflow: auto; outline: none; }
|
||||
button { border: 0; background: transparent; color: #e6edf3; cursor: pointer; }
|
||||
header button { font-size: 20px; color: #8b949e; }
|
||||
input { margin: 10px 12px; border: 1px solid #30363d; border-radius: 8px; background: #0d1117; color: #e6edf3; font: 14px system-ui, sans-serif; padding: 8px 10px; outline: none; }
|
||||
input:focus { border-color: #58a6ff; }
|
||||
.options button { display: block; width: 100%; padding: 10px 12px; border-bottom: 1px solid #21262d; text-align: left; }
|
||||
.options button.selected, .options button:hover { background: #0d2847; }
|
||||
small { display: block; margin-top: 4px; color: #8b949e; }
|
||||
.empty { padding: 24px; color: #8b949e; text-align: center; }
|
||||
`;
|
||||
|
||||
export const actionPaletteStyles = css`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { api, type CommandResult, type SessionActivity, type SessionInfo, type SessionStatus } from "../api";
|
||||
import { api, type CommandResult, type SessionActivity, type SessionInfo, type SessionStatus, type ThinkingLevel } from "../api";
|
||||
|
||||
const MESSAGE_PAGE_SIZE = 100;
|
||||
import { normalizeMessages, textMessage } from "../chatMessages";
|
||||
@@ -205,6 +205,68 @@ export class SessionController {
|
||||
}
|
||||
}
|
||||
|
||||
async listModels() {
|
||||
const session = this.getState().selectedSession;
|
||||
if (!session || session.archived === true) return [];
|
||||
try {
|
||||
return (await api.models(session.id)).models;
|
||||
} catch (error) {
|
||||
this.setState({ error: String(error) });
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async setModel(provider: string, modelId: string) {
|
||||
const session = this.getState().selectedSession;
|
||||
if (!session || session.archived === true) return;
|
||||
try {
|
||||
this.applyStatus(await api.setModel(session.id, provider, modelId));
|
||||
} catch (error) {
|
||||
this.setState({ error: String(error) });
|
||||
}
|
||||
}
|
||||
|
||||
async cycleModel(direction: "forward" | "backward") {
|
||||
const session = this.getState().selectedSession;
|
||||
if (!session || session.archived === true) return;
|
||||
try {
|
||||
this.applyStatus(await api.cycleModel(session.id, direction));
|
||||
} catch (error) {
|
||||
this.setState({ error: String(error) });
|
||||
}
|
||||
}
|
||||
|
||||
async listThinkingLevels() {
|
||||
const session = this.getState().selectedSession;
|
||||
if (!session || session.archived === true) return [];
|
||||
try {
|
||||
return (await api.thinkingLevels(session.id)).levels;
|
||||
} catch (error) {
|
||||
this.setState({ error: String(error) });
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async setThinkingLevel(level: ThinkingLevel) {
|
||||
const session = this.getState().selectedSession;
|
||||
if (!session || session.archived === true) return;
|
||||
try {
|
||||
this.applyStatus(await api.setThinkingLevel(session.id, level));
|
||||
} catch (error) {
|
||||
this.setState({ error: String(error) });
|
||||
}
|
||||
}
|
||||
|
||||
async cycleThinkingLevel() {
|
||||
const session = this.getState().selectedSession;
|
||||
if (!session || session.archived === true) return;
|
||||
try {
|
||||
this.applyStatus(await api.cycleThinkingLevel(session.id));
|
||||
} catch (error) {
|
||||
this.setState({ error: String(error) });
|
||||
}
|
||||
}
|
||||
|
||||
async stopActiveWork() {
|
||||
const session = this.getState().selectedSession;
|
||||
if (!session) return;
|
||||
|
||||
Reference in New Issue
Block a user