mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-10-29 18:52:45 +08:00
fix(vscode): share model picker expand state across webviews (#11910)
Persist the model selector expand/collapse preference in extension globalState instead of per-webview state, and broadcast changes through KiloConnectionService so every live webview (sidebar and Agent Manager panel) stays in sync.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Remember the model picker expand/collapse choice across the sidebar and the Agent Manager
|
||||
@@ -172,6 +172,10 @@ let maxCost = 0
|
||||
|
||||
type MessageLoadMode = "replace" | "prepend" | "focus" | "reconcile"
|
||||
type ContextMessage = { contextDirectory?: unknown }
|
||||
type TypedWebviewMessage = {
|
||||
type: string
|
||||
value?: unknown
|
||||
}
|
||||
type SandboxSupportClient = {
|
||||
support: (
|
||||
parameters: { directory?: string },
|
||||
@@ -392,6 +396,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
private unsubscribeLanguageChange: (() => void) | null = null
|
||||
private unsubscribeProfileChange: (() => void) | null = null
|
||||
private unsubscribeFavoritesChange: (() => void) | null = null
|
||||
private unsubscribeModelSelectorExpanded: (() => void) | null = null
|
||||
private unsubscribeMigrationComplete: (() => void) | null = null // legacy-migration
|
||||
private unsubscribeClearPendingPrompts: (() => void) | null = null
|
||||
private unsubscribeDirectoryProvider: (() => void) | null = null
|
||||
@@ -924,6 +929,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (await this.handleModelSelectorExpandedMessage(message)) return
|
||||
this.visibleTaskStreams.handle(message)
|
||||
switch (message.type) {
|
||||
case "webviewReady":
|
||||
@@ -1426,6 +1432,21 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
})
|
||||
}
|
||||
|
||||
private async handleModelSelectorExpandedMessage(message: TypedWebviewMessage): Promise<boolean> {
|
||||
if (message.type === "persistModelSelectorExpanded") {
|
||||
if (typeof message.value !== "boolean") return true
|
||||
await this.extensionContext?.globalState.update("modelSelectorExpanded", message.value)
|
||||
this.connectionService.notifyModelSelectorExpandedChanged(message.value)
|
||||
return true
|
||||
}
|
||||
if (message.type === "requestModelSelectorExpanded") {
|
||||
const value = this.extensionContext?.globalState.get("modelSelectorExpanded", true) ?? true
|
||||
this.postMessage({ type: "modelSelectorExpandedLoaded", value })
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private async toggleFavorite(message: {
|
||||
action: "add" | "remove"
|
||||
providerID: string
|
||||
@@ -1472,6 +1493,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
this.unsubscribeLanguageChange?.()
|
||||
this.unsubscribeProfileChange?.()
|
||||
this.unsubscribeFavoritesChange?.()
|
||||
this.unsubscribeModelSelectorExpanded?.()
|
||||
this.unsubscribeClearPendingPrompts?.()
|
||||
this.unsubscribeDirectoryProvider?.()
|
||||
|
||||
@@ -1574,6 +1596,11 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
this.postMessage({ type: "favoritesLoaded", favorites })
|
||||
})
|
||||
|
||||
// Subscribe to model-selector expand/collapse broadcast from other KiloProvider instances
|
||||
this.unsubscribeModelSelectorExpanded = this.connectionService.onModelSelectorExpandedChanged((value) => {
|
||||
this.postMessage({ type: "modelSelectorExpandedLoaded", value })
|
||||
})
|
||||
|
||||
// legacy-migration start
|
||||
// Subscribe to migration-complete broadcast from any KiloProvider instance
|
||||
this.unsubscribeMigrationComplete = this.connectionService.onMigrationComplete(() => {
|
||||
@@ -4218,6 +4245,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
this.unsubscribeLanguageChange?.()
|
||||
this.unsubscribeProfileChange?.()
|
||||
this.unsubscribeFavoritesChange?.()
|
||||
this.unsubscribeModelSelectorExpanded?.()
|
||||
this.unsubscribeMigrationComplete?.()
|
||||
this.unsubscribeClearPendingPrompts?.()
|
||||
this.unsubscribeDirectoryProvider?.()
|
||||
|
||||
@@ -15,6 +15,7 @@ type LanguageChangeListener = (locale: string) => void
|
||||
type ProfileChangeListener = (data: unknown) => void
|
||||
type MigrationCompleteListener = () => void
|
||||
type FavoritesChangeListener = (favorites: Array<{ providerID: string; modelID: string }>) => void
|
||||
type ModelSelectorExpandedListener = (value: boolean) => void
|
||||
type ClearPendingPromptsListener = () => void
|
||||
type DirectoryProvider = () => string[]
|
||||
|
||||
@@ -70,6 +71,7 @@ export class KiloConnectionService {
|
||||
private readonly profileChangeListeners: Set<ProfileChangeListener> = new Set()
|
||||
private readonly migrationCompleteListeners: Set<MigrationCompleteListener> = new Set()
|
||||
private readonly favoritesChangeListeners: Set<FavoritesChangeListener> = new Set()
|
||||
private readonly modelSelectorExpandedListeners: Set<ModelSelectorExpandedListener> = new Set()
|
||||
private readonly clearPendingPromptsListeners: Set<ClearPendingPromptsListener> = new Set()
|
||||
private readonly directoryProviders: Set<DirectoryProvider> = new Set()
|
||||
private rootDirectory: string | undefined = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath
|
||||
@@ -442,6 +444,25 @@ export class KiloConnectionService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to model-selector expand/collapse changes broadcast from any KiloProvider. Returns unsubscribe function.
|
||||
*/
|
||||
onModelSelectorExpandedChanged(listener: ModelSelectorExpandedListener): () => void {
|
||||
this.modelSelectorExpandedListeners.add(listener)
|
||||
return () => {
|
||||
this.modelSelectorExpandedListeners.delete(listener)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast a model-selector expand/collapse change to all subscribed KiloProvider instances.
|
||||
*/
|
||||
notifyModelSelectorExpandedChanged(value: boolean): void {
|
||||
for (const listener of this.modelSelectorExpandedListeners) {
|
||||
listener(value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to clear-pending-prompts broadcast. Returns unsubscribe function.
|
||||
* Fired after a config save drains all pending permissions/questions so each
|
||||
|
||||
@@ -65,6 +65,7 @@ function connection() {
|
||||
onProfileChanged: () => () => undefined,
|
||||
onMigrationComplete: () => () => undefined,
|
||||
onFavoritesChanged: () => () => undefined,
|
||||
onModelSelectorExpandedChanged: () => () => undefined,
|
||||
registerDirectoryProvider: () => () => undefined,
|
||||
getServerInfo: () => ({ port: 12345 }),
|
||||
getServerConfig: () => ({ baseUrl: "http://127.0.0.1:12345", password: "test" }),
|
||||
|
||||
@@ -187,6 +187,7 @@ function createConnection(client: ReturnType<typeof createClient>) {
|
||||
onProfileChanged: () => () => undefined,
|
||||
onMigrationComplete: () => () => undefined,
|
||||
onFavoritesChanged: () => () => undefined,
|
||||
onModelSelectorExpandedChanged: () => () => undefined,
|
||||
onClearPendingPrompts: () => () => undefined,
|
||||
registerDirectoryProvider: () => () => undefined,
|
||||
getServerInfo: () => ({ port: 12345 }),
|
||||
|
||||
@@ -86,6 +86,7 @@ function createConnection(client: ReturnType<typeof createClient>) {
|
||||
onProfileChanged: () => () => undefined,
|
||||
onMigrationComplete: () => () => undefined,
|
||||
onFavoritesChanged: () => () => undefined,
|
||||
onModelSelectorExpandedChanged: () => () => undefined,
|
||||
onClearPendingPrompts: () => () => undefined,
|
||||
registerDirectoryProvider: () => () => undefined,
|
||||
getServerInfo: () => ({ port: 12345 }),
|
||||
|
||||
@@ -155,9 +155,9 @@ export const ModelSelectorBase: Component<ModelSelectorBaseProps> = (props) => {
|
||||
})
|
||||
|
||||
const [open, setOpen] = createSignal(false)
|
||||
const [expanded, setExpanded] = createSignal(vscode.getModelSelectorExpanded())
|
||||
// Persist the user's expand/collapse choice so it is restored on reopen.
|
||||
createEffect(() => vscode.setModelSelectorExpanded(expanded()))
|
||||
// Shared, host-persisted expand/collapse preference (see VSCodeProvider).
|
||||
const expanded = vscode.getModelSelectorExpanded
|
||||
const setExpanded = vscode.setModelSelectorExpanded
|
||||
const [search, setSearch] = createSignal("")
|
||||
const [debouncedSearch, setDebouncedSearch] = createSignal("")
|
||||
const [selectedKey, setSelectedKey] = createSignal(CLEAR_KEY)
|
||||
@@ -828,13 +828,11 @@ export const ModelSelectorBase: Component<ModelSelectorBaseProps> = (props) => {
|
||||
aria-expanded={expanded()}
|
||||
aria-controls={previewID}
|
||||
onClick={() => {
|
||||
setExpanded((v) => {
|
||||
if (v) {
|
||||
setPreActiveKey(null)
|
||||
setPreviewKey(null)
|
||||
}
|
||||
return !v
|
||||
})
|
||||
if (expanded()) {
|
||||
setPreActiveKey(null)
|
||||
setPreviewKey(null)
|
||||
}
|
||||
setExpanded(!expanded())
|
||||
requestAnimationFrame(() => {
|
||||
searchRef?.focus()
|
||||
scrollRow(preActiveKey() ?? selectedKey(), "nearest")
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
* Provides access to the VS Code webview API for posting messages
|
||||
*/
|
||||
|
||||
import { createContext, useContext, onCleanup, ParentComponent } from "solid-js"
|
||||
import { createContext, useContext, onCleanup, ParentComponent, createSignal } from "solid-js"
|
||||
import type { VSCodeAPI, WebviewMessage, ExtensionMessage } from "../types/messages"
|
||||
|
||||
// Get the VS Code API (only available in webview context)
|
||||
let vscodeApi: VSCodeAPI | undefined
|
||||
const EXPANDED = "modelSelectorExpanded"
|
||||
|
||||
export function getVSCodeAPI(): VSCodeAPI {
|
||||
if (!vscodeApi) {
|
||||
@@ -44,6 +43,11 @@ export const VSCodeProvider: ParentComponent = (props) => {
|
||||
const api = getVSCodeAPI()
|
||||
const handlers = new Set<(message: ExtensionMessage) => void>()
|
||||
|
||||
// Model-selector expand/collapse preference. Stored in extension globalState
|
||||
// so it is shared across webviews (sidebar + agent-manager panel); a local
|
||||
// signal mirrors it for synchronous reads.
|
||||
const [expanded, setExpanded] = createSignal(true)
|
||||
|
||||
// Listen for messages from the extension
|
||||
const messageListener = (event: MessageEvent) => {
|
||||
const message = event.data as ExtensionMessage
|
||||
@@ -51,6 +55,10 @@ export const VSCodeProvider: ParentComponent = (props) => {
|
||||
}
|
||||
|
||||
window.addEventListener("message", messageListener)
|
||||
handlers.add((message) => {
|
||||
if (message?.type === "modelSelectorExpandedLoaded") setExpanded(message.value)
|
||||
})
|
||||
api.postMessage({ type: "requestModelSelectorExpanded" })
|
||||
|
||||
onCleanup(() => {
|
||||
window.removeEventListener("message", messageListener)
|
||||
@@ -67,13 +75,10 @@ export const VSCodeProvider: ParentComponent = (props) => {
|
||||
},
|
||||
getState: <T,>() => api.getState() as T | undefined,
|
||||
setState: <T,>(state: T) => api.setState(state),
|
||||
getModelSelectorExpanded: () => {
|
||||
const state = api.getState() as Record<string, unknown> | undefined
|
||||
return state?.[EXPANDED] !== false
|
||||
},
|
||||
getModelSelectorExpanded: expanded,
|
||||
setModelSelectorExpanded: (value: boolean) => {
|
||||
const state = (api.getState() as Record<string, unknown> | undefined) ?? {}
|
||||
api.setState({ ...state, [EXPANDED]: value })
|
||||
setExpanded(value)
|
||||
api.postMessage({ type: "persistModelSelectorExpanded", value })
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -738,6 +738,12 @@ export interface RecentsLoadedMessage {
|
||||
recents: ModelSelection[]
|
||||
}
|
||||
|
||||
// Persisted model-selector expand/collapse preference (extension → webview)
|
||||
export interface ModelSelectorExpandedLoadedMessage {
|
||||
type: "modelSelectorExpandedLoaded"
|
||||
value: boolean
|
||||
}
|
||||
|
||||
export interface FavoritesLoadedMessage {
|
||||
type: "favoritesLoaded"
|
||||
favorites: ModelSelection[]
|
||||
@@ -1200,6 +1206,7 @@ export type ExtensionMessage =
|
||||
| AnacondaDesktopExtensionMessage
|
||||
| CustomProviderModelsFetchedMessage
|
||||
| RecentsLoadedMessage
|
||||
| ModelSelectorExpandedLoadedMessage
|
||||
| FavoritesLoadedMessage
|
||||
| ModelSelectionsLoadedMessage
|
||||
| LanguageChangedMessage
|
||||
|
||||
@@ -1067,6 +1067,15 @@ export interface RequestRecentsMessage {
|
||||
type: "requestRecents"
|
||||
}
|
||||
|
||||
export interface PersistModelSelectorExpandedRequest {
|
||||
type: "persistModelSelectorExpanded"
|
||||
value: boolean
|
||||
}
|
||||
|
||||
export interface RequestModelSelectorExpandedMessage {
|
||||
type: "requestModelSelectorExpanded"
|
||||
}
|
||||
|
||||
export interface ToggleFavoriteRequest {
|
||||
type: "toggleFavorite"
|
||||
action: "add" | "remove"
|
||||
@@ -1349,6 +1358,8 @@ export type WebviewMessage =
|
||||
| FetchCustomProviderModelsMessage
|
||||
| PersistRecentsRequest
|
||||
| RequestRecentsMessage
|
||||
| PersistModelSelectorExpandedRequest
|
||||
| RequestModelSelectorExpandedMessage
|
||||
| ToggleFavoriteRequest
|
||||
| RequestFavoritesMessage
|
||||
| PersistModelSelectionRequest
|
||||
|
||||
Reference in New Issue
Block a user