Improve session defaults and onboarding UI

This commit is contained in:
Shantur Rathore
2025-11-07 21:22:46 +00:00
parent 7193103646
commit 5483932196
10 changed files with 254 additions and 119 deletions

View File

@@ -40,6 +40,14 @@ export default function AgentSelector(props: AgentSelectorProps) {
return filtered return filtered
}) })
createEffect(() => {
const list = availableAgents()
if (list.length === 0) return
if (!list.some((agent) => agent.name === props.currentAgent)) {
void props.onAgentChange(list[0].name)
}
})
createEffect(() => { createEffect(() => {
if (instanceAgents().length === 0) { if (instanceAgents().length === 0) {
fetchAgents(props.instanceId).catch(console.error) fetchAgents(props.instanceId).catch(console.error)

View File

@@ -3,6 +3,7 @@ import { Folder, Clock, Trash2, FolderPlus, Settings, ChevronDown, ChevronUp } f
import { recentFolders, removeRecentFolder, preferences, updateLastUsedBinary } from "../stores/preferences" import { recentFolders, removeRecentFolder, preferences, updateLastUsedBinary } from "../stores/preferences"
import OpenCodeBinarySelector from "./opencode-binary-selector" import OpenCodeBinarySelector from "./opencode-binary-selector"
import EnvironmentVariablesEditor from "./environment-variables-editor" import EnvironmentVariablesEditor from "./environment-variables-editor"
import Kbd from "./kbd"
interface FolderSelectionViewProps { interface FolderSelectionViewProps {
onSelectFolder: (folder?: string, binaryPath?: string) => void onSelectFolder: (folder?: string, binaryPath?: string) => void
@@ -14,35 +15,38 @@ const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
const [focusMode, setFocusMode] = createSignal<"recent" | "new" | null>("recent") const [focusMode, setFocusMode] = createSignal<"recent" | "new" | null>("recent")
const [showAdvanced, setShowAdvanced] = createSignal(false) const [showAdvanced, setShowAdvanced] = createSignal(false)
const [selectedBinary, setSelectedBinary] = createSignal(preferences().lastUsedBinary || "opencode") const [selectedBinary, setSelectedBinary] = createSignal(preferences().lastUsedBinary || "opencode")
let recentListRef: HTMLDivElement | undefined
const folders = () => recentFolders() const folders = () => recentFolders()
// Update selected binary when preferences change // Update selected binary when preferences change
createEffect(() => { createEffect(() => {
const lastUsed = preferences().lastUsedBinary const lastUsed = preferences().lastUsedBinary
if (lastUsed && lastUsed !== selectedBinary()) { if (lastUsed && lastUsed !== selectedBinary()) {
setSelectedBinary(lastUsed) setSelectedBinary(lastUsed)
} }
}) })
function scrollToIndex(index: number) { function scrollToIndex(index: number) {
const element = document.querySelector(`[data-folder-index="${index}"]`) const element = recentListRef?.querySelector(`[data-folder-index="${index}"]`)
if (element) { if (element) {
element.scrollIntoView({ block: "nearest", behavior: "auto" }) element.scrollIntoView({ block: "nearest", behavior: "auto" })
} }
} }
function handleKeyDown(e: KeyboardEvent) { function handleKeyDown(e: KeyboardEvent) {
const folderList = folders() const folderList = folders()
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { if ((e.metaKey || e.ctrlKey) && !e.shiftKey && e.key.toLowerCase() === "n") {
e.preventDefault() e.preventDefault()
handleBrowse() handleBrowse()
return return
} }
if (folderList.length === 0) return if (folderList.length === 0) return
if (e.key === "ArrowDown") { if (e.key === "ArrowDown") {
e.preventDefault() e.preventDefault()
const newIndex = Math.min(selectedIndex() + 1, folderList.length - 1) const newIndex = Math.min(selectedIndex() + 1, folderList.length - 1)
@@ -154,21 +158,22 @@ const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
} }
return ( return (
<div class="flex h-full w-full items-center justify-center" style="background-color: var(--surface-secondary)"> <div class="flex h-screen w-full items-start justify-center overflow-hidden py-6" style="background-color: var(--surface-secondary)">
<div class="w-full max-w-3xl px-8 py-12"> <div class="w-full max-w-3xl h-full max-h-[90vh] px-8 flex flex-col overflow-hidden">
<div class="mb-8 text-center"> <div class="mb-6 text-center shrink-0">
<div class="mb-4 flex justify-center"> <div class="mb-3 flex justify-center">
<Folder class="h-16 w-16 icon-muted" /> <Folder class="h-16 w-16 icon-muted" />
</div> </div>
<h1 class="mb-2 text-2xl font-semibold text-primary">Welcome to OpenCode</h1> <h1 class="mb-2 text-2xl font-semibold text-primary">Welcome to OpenCode</h1>
<p class="text-base text-secondary">Select a folder to start coding with AI</p> <p class="text-base text-secondary">Select a folder to start coding with AI</p>
</div> </div>
<div class="space-y-4 flex-1 min-h-0 overflow-hidden flex flex-col">
<div class="space-y-4 overflow-visible">
<Show <Show
when={folders().length > 0} when={folders().length > 0}
fallback={ fallback={
<div class="panel panel-empty-state"> <div class="panel panel-empty-state flex-1">
<div class="panel-empty-state-icon"> <div class="panel-empty-state-icon">
<Clock class="w-12 h-12 mx-auto" /> <Clock class="w-12 h-12 mx-auto" />
</div> </div>
@@ -177,22 +182,27 @@ const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
</div> </div>
} }
> >
<div class="panel"> <div class="panel flex-1 min-h-0 overflow-hidden">
<div class="panel-header"> <div class="panel-header">
<h2 class="panel-title">Recent Folders</h2> <h2 class="panel-title">Recent Folders</h2>
<p class="panel-subtitle"> <p class="panel-subtitle">
{folders().length} {folders().length === 1 ? "folder" : "folders"} available {folders().length} {folders().length === 1 ? "folder" : "folders"} available
</p> </p>
</div> </div>
<div class="panel-list"> <div
<For each={folders()}> class="panel-list max-h-[50vh] overflow-y-auto pr-1"
{(folder, index) => ( ref={(el) => (recentListRef = el)}
<div >
class="panel-list-item" <For each={folders()}>
classList={{ {(folder, index) => (
"panel-list-item-highlight": focusMode() === "recent" && selectedIndex() === index(), <div
}} data-folder-index={index()}
> class="panel-list-item"
classList={{
"panel-list-item-highlight": focusMode() === "recent" && selectedIndex() === index(),
}}
>
<div class="flex items-center w-full"> <div class="flex items-center w-full">
<button <button
class="panel-list-item-content w-full" class="panel-list-item-content w-full"
@@ -237,7 +247,7 @@ const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
</div> </div>
</Show> </Show>
<div class="panel"> <div class="panel shrink-0">
<div class="panel-header"> <div class="panel-header">
<h2 class="panel-title">Browse for Folder</h2> <h2 class="panel-title">Browse for Folder</h2>
<p class="panel-subtitle">Select any folder on your computer</p> <p class="panel-subtitle">Select any folder on your computer</p>
@@ -254,9 +264,7 @@ const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
<FolderPlus class="w-4 h-4" /> <FolderPlus class="w-4 h-4" />
<span>{props.isLoading ? "Opening..." : "Browse Folders"}</span> <span>{props.isLoading ? "Opening..." : "Browse Folders"}</span>
</div> </div>
<kbd class="kbd ml-2"> <Kbd shortcut="cmd+n" class="ml-2" />
Cmd+Enter
</kbd>
</button> </button>
</div> </div>
@@ -297,7 +305,7 @@ const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
</div> </div>
</div> </div>
<div class="mt-6 panel panel-footer"> <div class="mt-4 panel panel-footer shrink-0">
<div class="panel-footer-hints"> <div class="panel-footer-hints">
<Show when={folders().length > 0}> <Show when={folders().length > 0}>
<div class="flex items-center gap-1.5"> <div class="flex items-center gap-1.5">
@@ -315,7 +323,7 @@ const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
</div> </div>
</Show> </Show>
<div class="flex items-center gap-1.5"> <div class="flex items-center gap-1.5">
<kbd class="kbd">Cmd+Enter</kbd> <Kbd shortcut="cmd+n" />
<span>Browse</span> <span>Browse</span>
</div> </div>
</div> </div>

View File

@@ -1,7 +1,11 @@
import { Component, createSignal, Show, For, createEffect, onMount, onCleanup } from "solid-js" import { Component, createSignal, Show, For, createEffect, onMount, onCleanup, createMemo } from "solid-js"
import type { Instance } from "../types/instance" import type { Instance } from "../types/instance"
import { getParentSessions, createSession, setActiveParentSession, agents } from "../stores/sessions" import { getParentSessions, createSession, setActiveParentSession } from "../stores/sessions"
import InstanceInfo from "./instance-info" import InstanceInfo from "./instance-info"
import KeyboardHint from "./keyboard-hint"
import Kbd from "./kbd"
import { keyboardRegistry, type KeyboardShortcut } from "../lib/keyboard-registry"
import { isMac } from "../lib/keyboard-utils"
interface InstanceWelcomeViewProps { interface InstanceWelcomeViewProps {
@@ -9,20 +13,28 @@ interface InstanceWelcomeViewProps {
} }
const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => { const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
const [selectedAgent, setSelectedAgent] = createSignal<string>("")
const [isCreating, setIsCreating] = createSignal(false) const [isCreating, setIsCreating] = createSignal(false)
const [selectedIndex, setSelectedIndex] = createSignal(0) const [selectedIndex, setSelectedIndex] = createSignal(0)
const [focusMode, setFocusMode] = createSignal<"sessions" | "new-session" | null>("sessions") const [focusMode, setFocusMode] = createSignal<"sessions" | "new-session" | null>("sessions")
const parentSessions = () => getParentSessions(props.instance.id) const parentSessions = () => getParentSessions(props.instance.id)
const agentList = () => agents().get(props.instance.id) || [] const newSessionShortcut = createMemo<KeyboardShortcut>(() => {
const registered = keyboardRegistry.get("session-new")
createEffect(() => { if (registered) return registered
const list = agentList() return {
if (list.length > 0 && !selectedAgent()) { id: "session-new-display",
setSelectedAgent(list[0].name) key: "n",
modifiers: {
shift: true,
meta: isMac(),
ctrl: !isMac(),
},
handler: () => {},
description: "New Session",
context: "global",
} }
}) })
const newSessionShortcutString = createMemo(() => (isMac() ? "cmd+shift+n" : "ctrl+shift+n"))
createEffect(() => { createEffect(() => {
const sessions = parentSessions() const sessions = parentSessions()
@@ -45,7 +57,7 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
function handleKeyDown(e: KeyboardEvent) { function handleKeyDown(e: KeyboardEvent) {
const sessions = parentSessions() const sessions = parentSessions()
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === "n") {
e.preventDefault() e.preventDefault()
handleNewSession() handleNewSession()
return return
@@ -133,11 +145,11 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
} }
async function handleNewSession() { async function handleNewSession() {
if (isCreating() || agentList().length === 0) return if (isCreating()) return
setIsCreating(true) setIsCreating(true)
try { try {
const session = await createSession(props.instance.id, selectedAgent()) const session = await createSession(props.instance.id)
setActiveParentSession(props.instance.id, session.id) setActiveParentSession(props.instance.id, session.id)
} catch (error) { } catch (error) {
console.error("Failed to create session:", error) console.error("Failed to create session:", error)
@@ -153,7 +165,7 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
<Show <Show
when={parentSessions().length > 0} when={parentSessions().length > 0}
fallback={ fallback={
<div class="panel panel-empty-state flex-shrink-0"> <div class="panel panel-empty-state flex-1 flex flex-col justify-center">
<div class="panel-empty-state-icon"> <div class="panel-empty-state-icon">
<svg class="w-12 h-12 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-12 h-12 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path <path
@@ -169,14 +181,14 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
</div> </div>
} }
> >
<div class="panel flex-shrink-0"> <div class="panel flex flex-col flex-1 min-h-0">
<div class="panel-header"> <div class="panel-header">
<h2 class="panel-title">Resume Session</h2> <h2 class="panel-title">Resume Session</h2>
<p class="panel-subtitle"> <p class="panel-subtitle">
{parentSessions().length} {parentSessions().length === 1 ? "session" : "sessions"} available {parentSessions().length} {parentSessions().length === 1 ? "session" : "sessions"} available
</p> </p>
</div> </div>
<div class="panel-list"> <div class="panel-list panel-list--fill flex-1 min-h-0 overflow-auto">
<For each={parentSessions()}> <For each={parentSessions()}>
{(session, index) => ( {(session, index) => (
<div <div
@@ -228,35 +240,15 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
<div class="panel flex-shrink-0"> <div class="panel flex-shrink-0">
<div class="panel-header"> <div class="panel-header">
<h2 class="panel-title">Start New Session</h2> <h2 class="panel-title">Start New Session</h2>
<p class="panel-subtitle">Create a fresh conversation with your chosen agent</p> <p class="panel-subtitle">Well reuse your last agent/model automatically</p>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<div class="space-y-3"> <div class="space-y-3">
<Show when={agentList().length > 0}>
<div>
<label class="block text-xs font-medium text-secondary mb-1.5">Agent</label>
<select
class="selector-input w-full"
value={selectedAgent()}
onChange={(e) => setSelectedAgent(e.currentTarget.value)}
>
<For each={agentList()}>
{(agent) => (
<option value={agent.name}>
{agent.name}
{agent.description ? ` - ${agent.description}` : ""}
</option>
)}
</For>
</select>
</div>
</Show>
<button <button
type="button" type="button"
class="button-primary w-full flex items-center justify-center text-sm disabled:cursor-not-allowed" class="button-primary w-full flex items-center justify-center text-sm disabled:cursor-not-allowed"
onClick={handleNewSession} onClick={handleNewSession}
disabled={isCreating() || agentList().length === 0} disabled={isCreating()}
> >
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
{isCreating() ? ( {isCreating() ? (
@@ -273,11 +265,9 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg> </svg>
)} )}
<span>{agentList().length === 0 ? "Loading agents..." : "Create Session"}</span> <span>Create Session</span>
</div> </div>
<kbd class="kbd ml-2"> <Kbd shortcut={newSessionShortcutString()} class="ml-2" />
Cmd+Enter
</kbd>
</button> </button>
</div> </div>
</div> </div>
@@ -312,10 +302,7 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
<kbd class="kbd">Enter</kbd> <kbd class="kbd">Enter</kbd>
<span>Resume</span> <span>Resume</span>
</div> </div>
<div class="flex items-center gap-1.5"> <KeyboardHint shortcuts={[newSessionShortcut()]} separator="" />
<kbd class="kbd">Cmd+Enter</kbd>
<span>New Session</span>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -577,10 +577,11 @@ export default function MessageStream(props: MessageStreamProps) {
<div class="empty-state"> <div class="empty-state">
<div class="empty-state-content"> <div class="empty-state-content">
<h3>Start a conversation</h3> <h3>Start a conversation</h3>
<p>Type a message below or try:</p> <p>Type a message below or open the Command Palette:</p>
<ul> <ul>
<li> <li>
<code>/init-project</code> <span>Command Palette</span>
<Kbd shortcut="cmd+shift+p" class="ml-2" />
</li> </li>
<li>Ask about your codebase</li> <li>Ask about your codebase</li>
<li> <li>

View File

@@ -21,7 +21,12 @@ const SessionPicker: Component<SessionPickerProps> = (props) => {
createEffect(() => { createEffect(() => {
const list = agentList() const list = agentList()
if (list.length > 0 && !selectedAgent()) { if (list.length === 0) {
setSelectedAgent("")
return
}
const current = selectedAgent()
if (!current || !list.some((agent) => agent.name === current)) {
setSelectedAgent(list[0].name) setSelectedAgent(list[0].name)
} }
}) })

View File

@@ -1,5 +1,7 @@
import { instances, activeInstanceId, setActiveInstanceId } from "../stores/instances" import { instances, activeInstanceId, setActiveInstanceId } from "../stores/instances"
import { activeSessionId, setActiveSession, getSessions, activeParentSessionId } from "../stores/sessions" import { activeSessionId, setActiveSession, getSessions, activeParentSessionId } from "../stores/sessions"
import { keyboardRegistry } from "./keyboard-registry"
import { isMac } from "./keyboard-utils"
export function setupTabKeyboardShortcuts( export function setupTabKeyboardShortcuts(
handleNewInstance: () => void, handleNewInstance: () => void,
@@ -8,6 +10,22 @@ export function setupTabKeyboardShortcuts(
handleCloseSession: (instanceId: string, sessionId: string) => void, handleCloseSession: (instanceId: string, sessionId: string) => void,
handleCommandPalette: () => void, handleCommandPalette: () => void,
) { ) {
keyboardRegistry.register({
id: "session-new",
key: "n",
modifiers: {
shift: true,
meta: isMac(),
ctrl: !isMac(),
},
handler: () => {
const instanceId = activeInstanceId()
if (instanceId) void handleNewSession(instanceId)
},
description: "New Session",
context: "global",
})
window.addEventListener("keydown", (e) => { window.addEventListener("keydown", (e) => {
if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === "p") { if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === "p") {
e.preventDefault() e.preventDefault()
@@ -47,14 +65,6 @@ export function setupTabKeyboardShortcuts(
handleNewInstance() handleNewInstance()
} }
if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === "n") {
e.preventDefault()
const instanceId = activeInstanceId()
if (instanceId) {
handleNewSession(instanceId)
}
}
if ((e.metaKey || e.ctrlKey) && !e.shiftKey && e.key.toLowerCase() === "w") { if ((e.metaKey || e.ctrlKey) && !e.shiftKey && e.key.toLowerCase() === "w") {
e.preventDefault() e.preventDefault()
const instanceId = activeInstanceId() const instanceId = activeInstanceId()

View File

@@ -52,6 +52,8 @@ export class FileStorage {
preferences: { preferences: {
showThinkingBlocks: false, showThinkingBlocks: false,
environmentVariables: {}, environmentVariables: {},
modelRecents: [],
agentModelSelections: {},
}, },
recentFolders: [], recentFolders: [],
opencodeBinaries: [], opencodeBinaries: [],

View File

@@ -1,10 +1,21 @@
import { createSignal, onMount } from "solid-js" import { createSignal, onMount } from "solid-js"
import { storage, type ConfigData } from "../lib/storage" import { storage, type ConfigData } from "../lib/storage"
export interface ModelPreference {
providerId: string
modelId: string
}
export interface AgentModelSelections {
[instanceId: string]: Record<string, ModelPreference>
}
export interface Preferences { export interface Preferences {
showThinkingBlocks: boolean showThinkingBlocks: boolean
lastUsedBinary?: string lastUsedBinary?: string
environmentVariables?: Record<string, string> environmentVariables?: Record<string, string>
modelRecents?: ModelPreference[]
agentModelSelections?: AgentModelSelections
} }
export interface OpenCodeBinary { export interface OpenCodeBinary {
@@ -19,9 +30,12 @@ export interface RecentFolder {
} }
const MAX_RECENT_FOLDERS = 10 const MAX_RECENT_FOLDERS = 10
const MAX_RECENT_MODELS = 5
const defaultPreferences: Preferences = { const defaultPreferences: Preferences = {
showThinkingBlocks: false, showThinkingBlocks: false,
modelRecents: [],
agentModelSelections: {},
} }
const [preferences, setPreferences] = createSignal<Preferences>(defaultPreferences) const [preferences, setPreferences] = createSignal<Preferences>(defaultPreferences)
@@ -127,6 +141,37 @@ function removeEnvironmentVariable(key: string): void {
updateEnvironmentVariables(rest) updateEnvironmentVariables(rest)
} }
function addRecentModelPreference(model: ModelPreference): void {
if (!model.providerId || !model.modelId) return
const recents = preferences().modelRecents ?? []
const filtered = recents.filter((item) => item.providerId !== model.providerId || item.modelId !== model.modelId)
const updated = [model, ...filtered].slice(0, MAX_RECENT_MODELS)
updatePreferences({ modelRecents: updated })
}
function setAgentModelPreference(instanceId: string, agent: string, model: ModelPreference): void {
if (!instanceId || !agent || !model.providerId || !model.modelId) return
const selections = preferences().agentModelSelections ?? {}
const instanceSelections = selections[instanceId] ?? {}
const existing = instanceSelections[agent]
if (existing && existing.providerId === model.providerId && existing.modelId === model.modelId) {
return
}
updatePreferences({
agentModelSelections: {
...selections,
[instanceId]: {
...instanceSelections,
[agent]: model,
},
},
})
}
function getAgentModelPreference(instanceId: string, agent: string): ModelPreference | undefined {
return preferences().agentModelSelections?.[instanceId]?.[agent]
}
// Load config on mount and listen for changes from other instances // Load config on mount and listen for changes from other instances
onMount(() => { onMount(() => {
loadConfig() loadConfig()
@@ -154,4 +199,7 @@ export {
updateEnvironmentVariables, updateEnvironmentVariables,
addEnvironmentVariable, addEnvironmentVariable,
removeEnvironmentVariable, removeEnvironmentVariable,
addRecentModelPreference,
setAgentModelPreference,
getAgentModelPreference,
} }

View File

@@ -6,7 +6,7 @@ import { instances } from "./instances"
import { sseManager } from "../lib/sse-manager" import { sseManager } from "../lib/sse-manager"
import { decodeHtmlEntities } from "../lib/markdown" import { decodeHtmlEntities } from "../lib/markdown"
import { preferences } from "./preferences" import { preferences, addRecentModelPreference, getAgentModelPreference, setAgentModelPreference } from "./preferences"
interface SessionInfo { interface SessionInfo {
tokens: number tokens: number
@@ -330,6 +330,28 @@ async function fetchSessions(instanceId: string): Promise<void> {
} }
} }
function isModelValid(
instanceId: string,
model?: { providerId: string; modelId: string } | null,
): model is { providerId: string; modelId: string } {
if (!model?.providerId || !model.modelId) return false
const instanceProviders = providers().get(instanceId) || []
const provider = instanceProviders.find((p) => p.id === model.providerId)
if (!provider) return false
return provider.models.some((item) => item.id === model.modelId)
}
function getRecentModelPreferenceForInstance(
instanceId: string,
): { providerId: string; modelId: string } | undefined {
const recents = preferences().modelRecents ?? []
for (const item of recents) {
if (isModelValid(instanceId, item)) {
return item
}
}
}
async function getDefaultModel( async function getDefaultModel(
instanceId: string, instanceId: string,
agentName?: string, agentName?: string,
@@ -337,9 +359,16 @@ async function getDefaultModel(
const instanceProviders = providers().get(instanceId) || [] const instanceProviders = providers().get(instanceId) || []
const instanceAgents = agents().get(instanceId) || [] const instanceAgents = agents().get(instanceId) || []
if (agentName) {
const stored = getAgentModelPreference(instanceId, agentName)
if (isModelValid(instanceId, stored)) {
return stored
}
}
if (agentName) { if (agentName) {
const agent = instanceAgents.find((a) => a.name === agentName) const agent = instanceAgents.find((a) => a.name === agentName)
if (agent?.model?.providerId && agent.model.modelId) { if (agent && agent.model && isModelValid(instanceId, agent.model)) {
return { return {
providerId: agent.model.providerId, providerId: agent.model.providerId,
modelId: agent.model.modelId, modelId: agent.model.modelId,
@@ -347,25 +376,30 @@ async function getDefaultModel(
} }
} }
const anthropicProvider = instanceProviders.find((p) => p.id === "anthropic") const recent = getRecentModelPreferenceForInstance(instanceId)
if (anthropicProvider) { if (recent) {
const defaultModelId = anthropicProvider.defaultModelId || anthropicProvider.models[0]?.id return recent
if (defaultModelId) { }
return {
providerId: "anthropic", for (const provider of instanceProviders) {
modelId: defaultModelId, if (provider.defaultModelId) {
const model = provider.models.find((m) => m.id === provider.defaultModelId)
if (model) {
return {
providerId: provider.id,
modelId: model.id,
}
} }
} }
} }
if (instanceProviders.length > 0) { if (instanceProviders.length > 0) {
const firstProvider = instanceProviders[0] const firstProvider = instanceProviders[0]
const defaultModelId = firstProvider.defaultModelId || firstProvider.models[0]?.id const firstModel = firstProvider.models[0]
if (firstModel) {
if (defaultModelId) {
return { return {
providerId: firstProvider.id, providerId: firstProvider.id,
modelId: defaultModelId, modelId: firstModel.id,
} }
} }
} }
@@ -469,6 +503,10 @@ async function createSession(instanceId: string, agent?: string): Promise<Sessio
const defaultModel = await getDefaultModel(instanceId, selectedAgent) const defaultModel = await getDefaultModel(instanceId, selectedAgent)
if (selectedAgent && isModelValid(instanceId, defaultModel)) {
setAgentModelPreference(instanceId, selectedAgent, defaultModel)
}
setLoading((prev) => { setLoading((prev) => {
const next = { ...prev } const next = { ...prev }
next.creatingSession.set(instanceId, true) next.creatingSession.set(instanceId, true)
@@ -1457,16 +1495,27 @@ async function updateSessionAgent(instanceId: string, sessionId: string, agent:
throw new Error("Session not found") throw new Error("Session not found")
} }
const nextModel = await getDefaultModel(instanceId, agent)
const shouldApplyModel = isModelValid(instanceId, nextModel)
setSessions((prev) => { setSessions((prev) => {
const next = new Map(prev) const next = new Map(prev)
const instanceSessions = new Map(prev.get(instanceId)) const map = new Map(prev.get(instanceId))
const session = instanceSessions.get(sessionId) const current = map.get(sessionId)
if (session) { if (current) {
instanceSessions.set(sessionId, { ...session, agent }) map.set(sessionId, {
next.set(instanceId, instanceSessions) ...current,
agent,
model: shouldApplyModel ? nextModel : current.model,
})
next.set(instanceId, map)
} }
return next return next
}) })
if (agent && shouldApplyModel) {
setAgentModelPreference(instanceId, agent, nextModel)
}
} }
async function updateSessionModel( async function updateSessionModel(
@@ -1480,16 +1529,28 @@ async function updateSessionModel(
throw new Error("Session not found") throw new Error("Session not found")
} }
if (!isModelValid(instanceId, model)) {
console.warn("Invalid model selection", model)
return
}
const currentAgent = session.agent
setSessions((prev) => { setSessions((prev) => {
const next = new Map(prev) const next = new Map(prev)
const instanceSessions = new Map(prev.get(instanceId)) const map = new Map(prev.get(instanceId))
const session = instanceSessions.get(sessionId) const existing = map.get(sessionId)
if (session) { if (existing) {
instanceSessions.set(sessionId, { ...session, model }) map.set(sessionId, { ...existing, model })
next.set(instanceId, instanceSessions) next.set(instanceId, map)
} }
return next return next
}) })
if (currentAgent) {
setAgentModelPreference(instanceId, currentAgent, model)
}
addRecentModelPreference(model)
} }
function handleSessionCompacted(instanceId: string, event: any): void { function handleSessionCompacted(instanceId: string, event: any): void {

View File

@@ -1526,6 +1526,11 @@ button.button-primary {
@apply max-h-[400px] overflow-y-auto; @apply max-h-[400px] overflow-y-auto;
} }
.panel-list--fill {
max-height: none;
height: 100%;
}
.panel-list-item { .panel-list-item {
@apply border-b last:border-b-0 transition-colors w-full; @apply border-b last:border-b-0 transition-colors w-full;
border-color: var(--border-base); border-color: var(--border-base);