mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-10-24 01:24:18 +08:00
feat(settings): implement Notifications tab with globalState-backed client-side settings
This commit is contained in:
@@ -16,6 +16,8 @@ export class KiloProvider implements vscode.WebviewViewProvider {
|
||||
/** Cached configLoaded payload so requestConfig can be served before httpClient is ready */
|
||||
private cachedConfigMessage: unknown = null
|
||||
|
||||
private readonly globalState: vscode.Memento
|
||||
|
||||
private trackedSessionIds: Set<string> = new Set()
|
||||
private unsubscribeEvent: (() => void) | null = null
|
||||
private unsubscribeState: (() => void) | null = null
|
||||
@@ -24,7 +26,10 @@ export class KiloProvider implements vscode.WebviewViewProvider {
|
||||
constructor(
|
||||
private readonly extensionUri: vscode.Uri,
|
||||
private readonly connectionService: KiloConnectionService,
|
||||
) {}
|
||||
globalState: vscode.Memento,
|
||||
) {
|
||||
this.globalState = globalState
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience getter that returns the shared HttpClient or null if not yet connected.
|
||||
@@ -234,6 +239,12 @@ export class KiloProvider implements vscode.WebviewViewProvider {
|
||||
case "requestBrowserSettings":
|
||||
this.sendBrowserSettings()
|
||||
break
|
||||
case "requestLocalSettings":
|
||||
await this.handleRequestLocalSettings()
|
||||
break
|
||||
case "saveLocalSetting":
|
||||
await this.handleSaveLocalSetting(message.key, message.value)
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -316,6 +327,7 @@ export class KiloProvider implements vscode.WebviewViewProvider {
|
||||
await this.fetchAndSendProviders()
|
||||
await this.fetchAndSendAgents()
|
||||
await this.fetchAndSendConfig()
|
||||
await this.handleRequestLocalSettings()
|
||||
|
||||
console.log("[Kilo New] KiloProvider: ✅ initializeConnection completed successfully")
|
||||
} catch (error) {
|
||||
@@ -623,6 +635,32 @@ export class KiloProvider implements vscode.WebviewViewProvider {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send all local settings to the webview.
|
||||
*/
|
||||
private async handleRequestLocalSettings(): Promise<void> {
|
||||
const keys = [
|
||||
"notifications.agent",
|
||||
"notifications.permissions",
|
||||
"notifications.errors",
|
||||
"sounds.agent",
|
||||
"sounds.permissions",
|
||||
"sounds.errors",
|
||||
]
|
||||
const settings: Record<string, unknown> = {}
|
||||
for (const key of keys) {
|
||||
settings[key] = this.globalState.get(key)
|
||||
}
|
||||
this.postMessage({ type: "localSettingsLoaded", settings })
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a local setting to globalState.
|
||||
*/
|
||||
private async handleSaveLocalSetting(key: string, value: unknown): Promise<void> {
|
||||
await this.globalState.update(key, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle config update request from the webview.
|
||||
* Applies a partial config update, then re-fetches and pushes the full config.
|
||||
|
||||
@@ -23,7 +23,7 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
})
|
||||
|
||||
// Create the provider with shared service
|
||||
const provider = new KiloProvider(context.extensionUri, connectionService)
|
||||
const provider = new KiloProvider(context.extensionUri, connectionService, context.globalState)
|
||||
|
||||
// Register the webview view provider for the sidebar
|
||||
context.subscriptions.push(vscode.window.registerWebviewViewProvider(KiloProvider.viewType, provider))
|
||||
@@ -91,7 +91,7 @@ async function openKiloInNewTab(context: vscode.ExtensionContext, connectionServ
|
||||
dark: vscode.Uri.joinPath(context.extensionUri, "assets", "icons", "kilo-dark.svg"),
|
||||
}
|
||||
|
||||
const tabProvider = new KiloProvider(context.extensionUri, connectionService)
|
||||
const tabProvider = new KiloProvider(context.extensionUri, connectionService, context.globalState)
|
||||
tabProvider.resolveWebviewPanel(panel)
|
||||
|
||||
// Wait for the new panel to become active before locking the editor group.
|
||||
|
||||
@@ -1,29 +1,200 @@
|
||||
import { Component } from "solid-js"
|
||||
import { Component, createSignal, onCleanup } from "solid-js"
|
||||
import { useVSCode } from "../../context/vscode"
|
||||
import type { ExtensionMessage } from "../../types/messages"
|
||||
|
||||
const NotificationsTab: Component = () => {
|
||||
const vscode = useVSCode()
|
||||
|
||||
const [agentNotify, setAgentNotify] = createSignal(true)
|
||||
const [permNotify, setPermNotify] = createSignal(true)
|
||||
const [errorNotify, setErrorNotify] = createSignal(true)
|
||||
const [agentSound, setAgentSound] = createSignal("default")
|
||||
const [permSound, setPermSound] = createSignal("default")
|
||||
const [errorSound, setErrorSound] = createSignal("default")
|
||||
|
||||
// Listen for local settings loaded
|
||||
const unsubscribe = vscode.onMessage((message: ExtensionMessage) => {
|
||||
if (message.type !== "localSettingsLoaded") {
|
||||
return
|
||||
}
|
||||
const s = message.settings
|
||||
if (s["notifications.agent"] !== undefined) {
|
||||
setAgentNotify(s["notifications.agent"] as boolean)
|
||||
}
|
||||
if (s["notifications.permissions"] !== undefined) {
|
||||
setPermNotify(s["notifications.permissions"] as boolean)
|
||||
}
|
||||
if (s["notifications.errors"] !== undefined) {
|
||||
setErrorNotify(s["notifications.errors"] as boolean)
|
||||
}
|
||||
if (s["sounds.agent"] !== undefined) {
|
||||
setAgentSound(s["sounds.agent"] as string)
|
||||
}
|
||||
if (s["sounds.permissions"] !== undefined) {
|
||||
setPermSound(s["sounds.permissions"] as string)
|
||||
}
|
||||
if (s["sounds.errors"] !== undefined) {
|
||||
setErrorSound(s["sounds.errors"] as string)
|
||||
}
|
||||
})
|
||||
|
||||
onCleanup(unsubscribe)
|
||||
|
||||
// Request settings
|
||||
vscode.postMessage({ type: "requestLocalSettings" })
|
||||
|
||||
const saveSetting = (key: string, value: unknown) => {
|
||||
vscode.postMessage({ type: "saveLocalSetting", key, value })
|
||||
}
|
||||
|
||||
const selectStyle = {
|
||||
padding: "4px 8px",
|
||||
"border-radius": "4px",
|
||||
border: "1px solid var(--vscode-dropdown-border, var(--vscode-panel-border))",
|
||||
background: "var(--vscode-dropdown-background)",
|
||||
color: "var(--vscode-dropdown-foreground)",
|
||||
"font-size": "12px",
|
||||
"font-family": "var(--vscode-font-family)",
|
||||
cursor: "pointer",
|
||||
outline: "none",
|
||||
"min-width": "100px",
|
||||
}
|
||||
|
||||
interface RowProps {
|
||||
label: string
|
||||
description: string
|
||||
last?: boolean
|
||||
children: any
|
||||
}
|
||||
|
||||
const Row: Component<RowProps> = (props) => (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
"align-items": "center",
|
||||
"justify-content": "space-between",
|
||||
padding: "10px 12px",
|
||||
background: "var(--vscode-editor-background)",
|
||||
"border-bottom": props.last ? "none" : "1px solid var(--vscode-panel-border)",
|
||||
}}
|
||||
>
|
||||
<div style={{ flex: 1, "min-width": 0, "margin-right": "12px" }}>
|
||||
<div style={{ "font-size": "12px", "font-weight": "500", color: "var(--vscode-foreground)" }}>
|
||||
{props.label}
|
||||
</div>
|
||||
<div style={{ "font-size": "11px", color: "var(--vscode-descriptionForeground)", "margin-top": "2px" }}>
|
||||
{props.description}
|
||||
</div>
|
||||
</div>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Notification toggles */}
|
||||
<h4 style={{ "font-size": "13px", "margin-top": "0", "margin-bottom": "8px", color: "var(--vscode-foreground)" }}>
|
||||
Notifications
|
||||
</h4>
|
||||
<div
|
||||
style={{
|
||||
background: "var(--vscode-editor-background)",
|
||||
border: "1px solid var(--vscode-panel-border)",
|
||||
"border-radius": "4px",
|
||||
padding: "16px",
|
||||
overflow: "hidden",
|
||||
"margin-bottom": "16px",
|
||||
}}
|
||||
>
|
||||
<p
|
||||
style={{
|
||||
"font-size": "12px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
margin: 0,
|
||||
"line-height": "1.5",
|
||||
}}
|
||||
>
|
||||
<strong style={{ color: "var(--vscode-foreground)" }}>This section is not implemented yet.</strong> It will
|
||||
contain configuration options and explanatory text related to the selected settings category. During
|
||||
reimplementation, use this space to validate layout, spacing, scrolling behavior, and navigation state before
|
||||
wiring up real controls.
|
||||
</p>
|
||||
<Row label="Agent Completion" description="Show notification when agent completes a task">
|
||||
<label style={{ display: "flex", "align-items": "center", cursor: "pointer" }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={agentNotify()}
|
||||
onChange={(e) => {
|
||||
setAgentNotify(e.currentTarget.checked)
|
||||
saveSetting("notifications.agent", e.currentTarget.checked)
|
||||
}}
|
||||
style={{ cursor: "pointer" }}
|
||||
/>
|
||||
</label>
|
||||
</Row>
|
||||
<Row label="Permission Requests" description="Show notification on permission requests">
|
||||
<label style={{ display: "flex", "align-items": "center", cursor: "pointer" }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={permNotify()}
|
||||
onChange={(e) => {
|
||||
setPermNotify(e.currentTarget.checked)
|
||||
saveSetting("notifications.permissions", e.currentTarget.checked)
|
||||
}}
|
||||
style={{ cursor: "pointer" }}
|
||||
/>
|
||||
</label>
|
||||
</Row>
|
||||
<Row label="Errors" description="Show notification on errors" last>
|
||||
<label style={{ display: "flex", "align-items": "center", cursor: "pointer" }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={errorNotify()}
|
||||
onChange={(e) => {
|
||||
setErrorNotify(e.currentTarget.checked)
|
||||
saveSetting("notifications.errors", e.currentTarget.checked)
|
||||
}}
|
||||
style={{ cursor: "pointer" }}
|
||||
/>
|
||||
</label>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
{/* Sound settings */}
|
||||
<h4 style={{ "font-size": "13px", "margin-top": "0", "margin-bottom": "8px", color: "var(--vscode-foreground)" }}>
|
||||
Sounds
|
||||
</h4>
|
||||
<div
|
||||
style={{
|
||||
border: "1px solid var(--vscode-panel-border)",
|
||||
"border-radius": "4px",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<Row label="Agent Completion Sound" description="Sound to play when agent completes">
|
||||
<select
|
||||
style={selectStyle}
|
||||
value={agentSound()}
|
||||
onChange={(e) => {
|
||||
setAgentSound(e.currentTarget.value)
|
||||
saveSetting("sounds.agent", e.currentTarget.value)
|
||||
}}
|
||||
>
|
||||
<option value="default">Default</option>
|
||||
<option value="none">None</option>
|
||||
</select>
|
||||
</Row>
|
||||
<Row label="Permission Request Sound" description="Sound to play on permission requests">
|
||||
<select
|
||||
style={selectStyle}
|
||||
value={permSound()}
|
||||
onChange={(e) => {
|
||||
setPermSound(e.currentTarget.value)
|
||||
saveSetting("sounds.permissions", e.currentTarget.value)
|
||||
}}
|
||||
>
|
||||
<option value="default">Default</option>
|
||||
<option value="none">None</option>
|
||||
</select>
|
||||
</Row>
|
||||
<Row label="Error Sound" description="Sound to play on errors" last>
|
||||
<select
|
||||
style={selectStyle}
|
||||
value={errorSound()}
|
||||
onChange={(e) => {
|
||||
setErrorSound(e.currentTarget.value)
|
||||
saveSetting("sounds.errors", e.currentTarget.value)
|
||||
}}
|
||||
>
|
||||
<option value="default">Default</option>
|
||||
<option value="none">None</option>
|
||||
</select>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -450,6 +450,11 @@ export interface ConfigUpdatedMessage {
|
||||
config: Config
|
||||
}
|
||||
|
||||
export interface LocalSettingsLoadedMessage {
|
||||
type: "localSettingsLoaded"
|
||||
settings: Record<string, unknown>
|
||||
}
|
||||
|
||||
export type ExtensionMessage =
|
||||
| ReadyMessage
|
||||
| ConnectionStateMessage
|
||||
@@ -478,6 +483,7 @@ export type ExtensionMessage =
|
||||
| BrowserSettingsLoadedMessage
|
||||
| ConfigLoadedMessage
|
||||
| ConfigUpdatedMessage
|
||||
| LocalSettingsLoadedMessage
|
||||
|
||||
// ============================================
|
||||
// Messages FROM webview TO extension
|
||||
@@ -607,6 +613,16 @@ export interface UpdateConfigMessage {
|
||||
config: Partial<Config>
|
||||
}
|
||||
|
||||
export interface SaveLocalSettingMessage {
|
||||
type: "saveLocalSetting"
|
||||
key: string
|
||||
value: unknown
|
||||
}
|
||||
|
||||
export interface RequestLocalSettingsMessage {
|
||||
type: "requestLocalSettings"
|
||||
}
|
||||
|
||||
export type WebviewMessage =
|
||||
| SendMessageRequest
|
||||
| AbortRequest
|
||||
@@ -633,6 +649,8 @@ export type WebviewMessage =
|
||||
| RequestBrowserSettingsMessage
|
||||
| RequestConfigMessage
|
||||
| UpdateConfigMessage
|
||||
| SaveLocalSettingMessage
|
||||
| RequestLocalSettingsMessage
|
||||
|
||||
// ============================================
|
||||
// VS Code API type
|
||||
|
||||
Reference in New Issue
Block a user