Merge pull request #7492 from Kilo-Org/kirillk/banner

feat(vscode): improve notifications UX
This commit is contained in:
Mark IJbema
2026-03-24 09:02:26 +01:00
committed by GitHub
20 changed files with 169 additions and 31 deletions
@@ -1,11 +1,9 @@
import { Component, Show, createMemo, createSignal } from "solid-js"
import { Button } from "@kilocode/kilo-ui/button"
import { IconButton } from "@kilocode/kilo-ui/icon-button"
import { Icon } from "@kilocode/kilo-ui/icon"
import { Component, Show, createEffect, createMemo, createSignal } from "solid-js"
import { useNotifications } from "../../context/notifications"
import { useVSCode } from "../../context/vscode"
import { useSession } from "../../context/session"
import { useProvider } from "../../context/provider"
import { useLanguage } from "../../context/language"
import { KILO_PROVIDER_ID } from "../../../../src/shared/provider-model"
import { TelemetryEventName } from "../../../../src/services/telemetry/types"
@@ -14,6 +12,7 @@ export const KiloNotifications: Component = () => {
const vscode = useVSCode()
const session = useSession()
const provider = useProvider()
const language = useLanguage()
const [index, setIndex] = createSignal(0)
const items = filteredNotifications
@@ -21,18 +20,24 @@ export const KiloNotifications: Component = () => {
const safeIndex = () => Math.min(index(), Math.max(0, total() - 1))
const current = createMemo(() => (total() === 0 ? undefined : items()[safeIndex()]))
const prev = () => setIndex((i) => (i - 1 + total()) % total())
const next = () => setIndex((i) => (i + 1) % total())
// Clamp index whenever the list shrinks so navigation always reflects reality
createEffect(() => {
const max = Math.max(0, total() - 1)
if (index() > max) setIndex(max)
})
const handleAction = (url: string) => {
vscode.postMessage({ type: "openExternal", url })
}
const handleDismiss = () => {
const n = current()
if (!n) return
dismiss(n.id)
setIndex((i) => Math.min(i, Math.max(0, total() - 2)))
const isLast = () => safeIndex() === total() - 1
const handleNext = () => {
if (isLast()) {
for (const n of items()) dismiss(n.id)
} else {
setIndex(safeIndex() + 1)
}
}
/**
@@ -73,35 +78,36 @@ export const KiloNotifications: Component = () => {
<div class="kilo-notifications-card">
<div class="kilo-notifications-header">
<span class="kilo-notifications-title">{current()?.title}</span>
<IconButton size="small" variant="ghost" icon="close" onClick={handleDismiss} title="Dismiss" />
<Show when={total() > 1}>
<span class="kilo-notifications-nav-count">
{safeIndex() + 1} / {total()}
</span>
</Show>
</div>
<p class="kilo-notifications-message">{current()?.message}</p>
<div class="kilo-notifications-footer">
<Show when={total() > 1}>
<div class="kilo-notifications-nav">
<button class="kilo-notifications-nav-btn" onClick={prev} title="Previous">
<Icon name="arrow-left" size="small" />
</button>
<span class="kilo-notifications-nav-count">
{safeIndex() + 1} / {total()}
</span>
<button class="kilo-notifications-nav-btn" onClick={next} title="Next">
<Icon name="arrow-right" size="small" />
</button>
</div>
</Show>
<Show when={canSwitchModel()}>
<Button variant="primary" size="small" onClick={handleTryModel}>
Try model
</Button>
<button class="kilo-notifications-action-btn" onClick={handleTryModel}>
{language.t("notifications.action.tryModel")}
</button>
</Show>
<Show when={current()?.action}>
{(action) => (
<Button variant="primary" size="small" onClick={() => handleAction(action().actionURL)}>
<button class="kilo-notifications-action-btn" onClick={() => handleAction(action().actionURL)}>
{action().actionText}
</Button>
</button>
)}
</Show>
<div class="kilo-notifications-next-group">
<Show when={safeIndex() > 0}>
<button class="kilo-notifications-back-link" onClick={() => setIndex(safeIndex() - 1)}>
{language.t("notifications.action.previous")}
</button>
</Show>
<button class="kilo-notifications-primary-btn" onClick={handleNext}>
{isLast() ? language.t("notifications.action.close") : language.t("notifications.action.next")}
</button>
</div>
</div>
</div>
</div>
@@ -1188,4 +1188,8 @@ export const dict = {
"settings.saveBar.warning.many": "عدة جلسات تعمل وستتوقف",
"settings.saveBar.saveAnyway": "حفظ على أي حال",
"settings.saveBar.cancel": "إلغاء",
"notifications.action.previous": "Previous",
"notifications.action.next": "Next",
"notifications.action.close": "Close",
"notifications.action.tryModel": "Try model",
}
@@ -1211,4 +1211,8 @@ export const dict = {
"settings.saveBar.warning.many": "Várias sessões estão em execução e serão interrompidas",
"settings.saveBar.saveAnyway": "Salvar mesmo assim",
"settings.saveBar.cancel": "Cancelar",
"notifications.action.previous": "Previous",
"notifications.action.next": "Next",
"notifications.action.close": "Close",
"notifications.action.tryModel": "Try model",
}
@@ -1210,4 +1210,8 @@ export const dict = {
"settings.saveBar.warning.many": "Nekoliko sesija je pokrenuto i bit će prekinuto",
"settings.saveBar.saveAnyway": "Spremi svejedno",
"settings.saveBar.cancel": "Otkaži",
"notifications.action.previous": "Previous",
"notifications.action.next": "Next",
"notifications.action.close": "Close",
"notifications.action.tryModel": "Try model",
}
@@ -1203,4 +1203,8 @@ export const dict = {
"settings.saveBar.warning.many": "Flere sessioner kører og vil blive afbrudt",
"settings.saveBar.saveAnyway": "Gem alligevel",
"settings.saveBar.cancel": "Annuller",
"notifications.action.previous": "Previous",
"notifications.action.next": "Next",
"notifications.action.close": "Close",
"notifications.action.tryModel": "Try model",
}
@@ -1225,4 +1225,8 @@ export const dict = {
"settings.saveBar.warning.many": "Mehrere Sitzungen laufen und werden unterbrochen",
"settings.saveBar.saveAnyway": "Trotzdem speichern",
"settings.saveBar.cancel": "Abbrechen",
"notifications.action.previous": "Previous",
"notifications.action.next": "Next",
"notifications.action.close": "Close",
"notifications.action.tryModel": "Try model",
} satisfies Partial<Record<Keys, string>>
@@ -1216,4 +1216,9 @@ export const dict = {
"settings.saveBar.warning.many": "Several sessions are running and will be interrupted",
"settings.saveBar.saveAnyway": "Save anyway",
"settings.saveBar.cancel": "Cancel",
"notifications.action.previous": "Previous",
"notifications.action.next": "Next",
"notifications.action.close": "Close",
"notifications.action.tryModel": "Try model",
}
@@ -1214,4 +1214,8 @@ export const dict = {
"settings.saveBar.warning.many": "Varias sesiones están en ejecución y se interrumpirán",
"settings.saveBar.saveAnyway": "Guardar de todas formas",
"settings.saveBar.cancel": "Cancelar",
"notifications.action.previous": "Previous",
"notifications.action.next": "Next",
"notifications.action.close": "Close",
"notifications.action.tryModel": "Try model",
}
@@ -1226,4 +1226,8 @@ export const dict = {
"settings.saveBar.warning.many": "Plusieurs sessions sont en cours et seront interrompues",
"settings.saveBar.saveAnyway": "Enregistrer quand même",
"settings.saveBar.cancel": "Annuler",
"notifications.action.previous": "Previous",
"notifications.action.next": "Next",
"notifications.action.close": "Close",
"notifications.action.tryModel": "Try model",
}
@@ -1200,4 +1200,8 @@ export const dict = {
"settings.saveBar.warning.many": "複数のセッションが実行中で中断されます",
"settings.saveBar.saveAnyway": "それでも保存",
"settings.saveBar.cancel": "キャンセル",
"notifications.action.previous": "Previous",
"notifications.action.next": "Next",
"notifications.action.close": "Close",
"notifications.action.tryModel": "Try model",
}
@@ -1191,4 +1191,8 @@ export const dict = {
"settings.saveBar.warning.many": "여러 세션이 실행 중이며 중단됩니다",
"settings.saveBar.saveAnyway": "그래도 저장",
"settings.saveBar.cancel": "취소",
"notifications.action.previous": "Previous",
"notifications.action.next": "Next",
"notifications.action.close": "Close",
"notifications.action.tryModel": "Try model",
}
@@ -1232,4 +1232,8 @@ export const dict = {
"settings.saveBar.warning.many": "Meerdere sessies zijn actief en worden onderbroken",
"settings.saveBar.saveAnyway": "Toch opslaan",
"settings.saveBar.cancel": "Annuleren",
"notifications.action.previous": "Previous",
"notifications.action.next": "Next",
"notifications.action.close": "Close",
"notifications.action.tryModel": "Try model",
}
@@ -1201,4 +1201,8 @@ export const dict = {
"settings.saveBar.warning.many": "Flere økter kjører og vil bli avbrutt",
"settings.saveBar.saveAnyway": "Lagre uansett",
"settings.saveBar.cancel": "Avbryt",
"notifications.action.previous": "Previous",
"notifications.action.next": "Next",
"notifications.action.close": "Close",
"notifications.action.tryModel": "Try model",
} satisfies Partial<Record<Keys, string>>
@@ -1208,4 +1208,8 @@ export const dict = {
"settings.saveBar.warning.many": "Kilka sesji jest uruchomionych i zostanie przerwanych",
"settings.saveBar.saveAnyway": "Zapisz mimo to",
"settings.saveBar.cancel": "Anuluj",
"notifications.action.previous": "Previous",
"notifications.action.next": "Next",
"notifications.action.close": "Close",
"notifications.action.tryModel": "Try model",
}
@@ -1207,4 +1207,8 @@ export const dict = {
"settings.saveBar.warning.many": "Несколько сеансов выполняются и будут прерваны",
"settings.saveBar.saveAnyway": "Сохранить в любом случае",
"settings.saveBar.cancel": "Отмена",
"notifications.action.previous": "Previous",
"notifications.action.next": "Next",
"notifications.action.close": "Close",
"notifications.action.tryModel": "Try model",
}
@@ -1187,4 +1187,8 @@ export const dict = {
"settings.saveBar.warning.many": "มีหลายเซสชันกำลังทำงานและจะถูกขัดจังหวะ",
"settings.saveBar.saveAnyway": "บันทึกต่อไป",
"settings.saveBar.cancel": "ยกเลิก",
"notifications.action.previous": "Previous",
"notifications.action.next": "Next",
"notifications.action.close": "Close",
"notifications.action.tryModel": "Try model",
}
@@ -1226,4 +1226,8 @@ export const dict = {
"settings.saveBar.warning.many": "Birden fazla oturum çalışıyor ve kesintiye uğrayacak",
"settings.saveBar.saveAnyway": "Yine de kaydet",
"settings.saveBar.cancel": "İptal",
"notifications.action.previous": "Previous",
"notifications.action.next": "Next",
"notifications.action.close": "Close",
"notifications.action.tryModel": "Try model",
}
@@ -1171,4 +1171,8 @@ export const dict = {
"settings.saveBar.warning.many": "多个会话正在运行,将被中断",
"settings.saveBar.saveAnyway": "仍然保存",
"settings.saveBar.cancel": "取消",
"notifications.action.previous": "Previous",
"notifications.action.next": "Next",
"notifications.action.close": "Close",
"notifications.action.tryModel": "Try model",
} satisfies Partial<Record<Keys, string>>
@@ -1172,4 +1172,8 @@ export const dict = {
"settings.saveBar.warning.many": "多個工作階段正在執行,將被中斷",
"settings.saveBar.saveAnyway": "仍然儲存",
"settings.saveBar.cancel": "取消",
"notifications.action.previous": "Previous",
"notifications.action.next": "Next",
"notifications.action.close": "Close",
"notifications.action.tryModel": "Try model",
} satisfies Partial<Record<Keys, string>>
@@ -2124,7 +2124,7 @@
top: 0;
left: 0;
right: 0;
z-index: 10;
z-index: 1000;
display: flex;
flex-direction: column;
border-bottom: 1px solid var(--vscode-panel-border);
@@ -2185,6 +2185,65 @@
gap: 2px;
}
.kilo-notifications-next-group {
display: flex;
align-items: center;
gap: 6px;
}
.kilo-notifications-action-btn {
display: inline-flex;
align-items: center;
justify-content: center;
height: 24px;
padding: 0 8px;
background: none;
border: 1px solid var(--vscode-focusBorder);
border-radius: 3px;
color: var(--vscode-focusBorder);
font-size: 12px;
font-family: var(--vscode-font-family);
cursor: pointer;
white-space: nowrap;
}
.kilo-notifications-action-btn:hover {
background-color: var(--vscode-toolbar-hoverBackground);
}
.kilo-notifications-primary-btn {
display: inline-flex;
align-items: center;
justify-content: center;
height: 24px;
padding: 0 8px;
background-color: var(--vscode-button-background);
border: 1px solid transparent;
border-radius: 3px;
color: var(--vscode-button-foreground);
font-size: 12px;
font-family: var(--vscode-font-family);
cursor: pointer;
white-space: nowrap;
}
.kilo-notifications-primary-btn:hover {
background-color: var(--vscode-button-hoverBackground);
}
.kilo-notifications-back-link {
background: none;
border: none;
padding: 0;
cursor: pointer;
color: var(--vscode-descriptionForeground);
font-size: 12px;
}
.kilo-notifications-back-link:hover {
color: var(--vscode-foreground);
}
.kilo-notifications-nav-btn {
background: none;
border: none;