Clean up legacy instance shell and theme additions
This commit is contained in:
125
package-lock.json
generated
125
package-lock.json
generated
@@ -1296,6 +1296,16 @@
|
|||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@popperjs/core": {
|
||||||
|
"version": "2.11.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||||
|
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/popperjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.52.5",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz",
|
||||||
@@ -1531,6 +1541,109 @@
|
|||||||
"solid-js": "^1.8.6"
|
"solid-js": "^1.8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@suid/base": {
|
||||||
|
"version": "0.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@suid/base/-/base-0.11.0.tgz",
|
||||||
|
"integrity": "sha512-jNe+LlXuxfkSZo8/MP9koqYYWswucDWSCwc7ViqUhQ0Y/V7sP2RiQ/Bnms+ePSMBZsk5k1b9fAjvj7DtNbbHXw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@popperjs/core": "^2.11.8",
|
||||||
|
"@suid/css": "0.4.1",
|
||||||
|
"@suid/system": "0.14.0",
|
||||||
|
"@suid/types": "0.8.0",
|
||||||
|
"@suid/utils": "0.11.0",
|
||||||
|
"clsx": "^2.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"solid-js": "^1.9.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@suid/css": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@suid/css/-/css-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-Hsi4O3dBOm7rrlqKoWfNoTeRFAXm/7TPaeEmyxNx+wFaT3eROjMVdhadAIiagFT+PsHrq/6fDauUI5TkL+5Zvg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@suid/icons-material": {
|
||||||
|
"version": "0.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@suid/icons-material/-/icons-material-0.9.0.tgz",
|
||||||
|
"integrity": "sha512-2idgaT/JARd12dwDfocZBQizaiZVgR0ujRsVc61OlAuPZbeH+3TrSxUJkE3Z7+TPftw9+6p0A24GhJjJLvi6RQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@suid/material": "0.19.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"solid-js": "^1.9.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@suid/material": {
|
||||||
|
"version": "0.19.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@suid/material/-/material-0.19.0.tgz",
|
||||||
|
"integrity": "sha512-vfudxYpHdur5CWTjd3eBb7q1b6A9X/pDWTEf2twc0gXVTcErS9VtY/VPBLa65AzO2SPJsdjAE+BCdVZiXASBbA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@suid/base": "0.11.0",
|
||||||
|
"@suid/css": "0.4.1",
|
||||||
|
"@suid/system": "0.14.0",
|
||||||
|
"@suid/types": "0.8.0",
|
||||||
|
"@suid/utils": "0.11.0",
|
||||||
|
"clsx": "^2.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"solid-js": "^1.9.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@suid/styled-engine": {
|
||||||
|
"version": "0.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@suid/styled-engine/-/styled-engine-0.9.0.tgz",
|
||||||
|
"integrity": "sha512-IfNHjQ3Im63mFIjFl/doiwdn5qbwgcwi/vUXnX7dmIUC/Cw1f3LPhzVT9V8Z3eqyvvFToy53O+BsuLy2e/WmDw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@suid/css": "0.4.1",
|
||||||
|
"@suid/utils": "0.11.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"solid-js": "^1.9.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@suid/system": {
|
||||||
|
"version": "0.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@suid/system/-/system-0.14.0.tgz",
|
||||||
|
"integrity": "sha512-aRVilPP53hHkqyAyQp2pasT/u8aQCcELwU4kFDnt3b+rj4fsPQRlhMumlX5mZ5aijIboH1CngU6TDG6Z9Mr3UA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@suid/css": "0.4.1",
|
||||||
|
"@suid/styled-engine": "0.9.0",
|
||||||
|
"@suid/types": "0.8.0",
|
||||||
|
"@suid/utils": "0.11.0",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"csstype": "^3.1.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"solid-js": "^1.9.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@suid/types": {
|
||||||
|
"version": "0.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@suid/types/-/types-0.8.0.tgz",
|
||||||
|
"integrity": "sha512-/Z2abkbypMjF6ygSpnjqnWohcmPqvgw8Xpx1wPPHeh+LajBP2imNT6uEa5dBqNEkJY8O3wEUCVqErAad/rmn5Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"solid-js": "^1.9.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@suid/utils": {
|
||||||
|
"version": "0.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@suid/utils/-/utils-0.11.0.tgz",
|
||||||
|
"integrity": "sha512-dk+6YJkex9kcU2qQHCOk8J0/zkOKKbng0SsjC0LBLyBrf2OC3OtDQq7o22pH3m/8CU/0M6uyM7tnyzZA4eWF3Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@suid/types": "0.8.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"solid-js": "^1.9.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@swc/helpers": {
|
"node_modules/@swc/helpers": {
|
||||||
"version": "0.5.17",
|
"version": "0.5.17",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
|
||||||
@@ -3102,6 +3215,15 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/clsx": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
@@ -8895,6 +9017,9 @@
|
|||||||
"@kobalte/core": "0.13.11",
|
"@kobalte/core": "0.13.11",
|
||||||
"@opencode-ai/sdk": "^1.0.138",
|
"@opencode-ai/sdk": "^1.0.138",
|
||||||
"@solidjs/router": "^0.13.0",
|
"@solidjs/router": "^0.13.0",
|
||||||
|
"@suid/icons-material": "^0.9.0",
|
||||||
|
"@suid/material": "^0.19.0",
|
||||||
|
"@suid/system": "^0.14.0",
|
||||||
"debug": "^4.4.3",
|
"debug": "^4.4.3",
|
||||||
"github-markdown-css": "^5.8.1",
|
"github-markdown-css": "^5.8.1",
|
||||||
"lucide-solid": "^0.300.0",
|
"lucide-solid": "^0.300.0",
|
||||||
|
|||||||
@@ -14,6 +14,9 @@
|
|||||||
"@kobalte/core": "0.13.11",
|
"@kobalte/core": "0.13.11",
|
||||||
"@opencode-ai/sdk": "^1.0.138",
|
"@opencode-ai/sdk": "^1.0.138",
|
||||||
"@solidjs/router": "^0.13.0",
|
"@solidjs/router": "^0.13.0",
|
||||||
|
"@suid/icons-material": "^0.9.0",
|
||||||
|
"@suid/material": "^0.19.0",
|
||||||
|
"@suid/system": "^0.14.0",
|
||||||
"debug": "^4.4.3",
|
"debug": "^4.4.3",
|
||||||
"github-markdown-css": "^5.8.1",
|
"github-markdown-css": "^5.8.1",
|
||||||
"lucide-solid": "^0.300.0",
|
"lucide-solid": "^0.300.0",
|
||||||
|
|||||||
@@ -1,356 +0,0 @@
|
|||||||
import { For, Show, createEffect, createMemo, createSignal, onCleanup, onMount, type Component } from "solid-js"
|
|
||||||
import type { Accessor } from "solid-js"
|
|
||||||
import type { Instance } from "../../types/instance"
|
|
||||||
import type { Command } from "../../lib/commands"
|
|
||||||
import { activeParentSessionId, activeSessionId as activeSessionMap, getSessionFamily, setActiveSession } from "../../stores/sessions"
|
|
||||||
import { keyboardRegistry, type KeyboardShortcut } from "../../lib/keyboard-registry"
|
|
||||||
import { messageStoreBus } from "../../stores/message-v2/bus"
|
|
||||||
import { clearSessionRenderCache } from "../message-block"
|
|
||||||
import { buildCustomCommandEntries } from "../../lib/command-utils"
|
|
||||||
import { getCommands as getInstanceCommands } from "../../stores/commands"
|
|
||||||
import { isOpen as isCommandPaletteOpen, hideCommandPalette } from "../../stores/command-palette"
|
|
||||||
import SessionList from "../session-list"
|
|
||||||
import KeyboardHint from "../keyboard-hint"
|
|
||||||
import InstanceWelcomeView from "../instance-welcome-view"
|
|
||||||
import InfoView from "../info-view"
|
|
||||||
import AgentSelector from "../agent-selector"
|
|
||||||
import ModelSelector from "../model-selector"
|
|
||||||
import CommandPalette from "../command-palette"
|
|
||||||
import Kbd from "../kbd"
|
|
||||||
import ContextUsagePanel from "../session/context-usage-panel"
|
|
||||||
import SessionView from "../session/session-view"
|
|
||||||
import { getLogger } from "../../lib/logger"
|
|
||||||
const log = getLogger("session")
|
|
||||||
|
|
||||||
|
|
||||||
interface InstanceShellProps {
|
|
||||||
instance: Instance
|
|
||||||
escapeInDebounce: boolean
|
|
||||||
paletteCommands: Accessor<Command[]>
|
|
||||||
onCloseSession: (sessionId: string) => Promise<void> | void
|
|
||||||
onNewSession: () => Promise<void> | void
|
|
||||||
handleSidebarAgentChange: (sessionId: string, agent: string) => Promise<void>
|
|
||||||
handleSidebarModelChange: (sessionId: string, model: { providerId: string; modelId: string }) => Promise<void>
|
|
||||||
onExecuteCommand: (command: Command) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEFAULT_SESSION_SIDEBAR_WIDTH = 350
|
|
||||||
const MOBILE_SIDEBAR_BREAKPOINT = 1024
|
|
||||||
const SESSION_CACHE_LIMIT = 2
|
|
||||||
|
|
||||||
const InstanceShell: Component<InstanceShellProps> = (props) => {
|
|
||||||
const [sessionSidebarWidth, setSessionSidebarWidth] = createSignal(DEFAULT_SESSION_SIDEBAR_WIDTH)
|
|
||||||
const [isCompactLayout, setIsCompactLayout] = createSignal(false)
|
|
||||||
const [isSidebarOpen, setIsSidebarOpen] = createSignal(true)
|
|
||||||
const [cachedSessionIds, setCachedSessionIds] = createSignal<string[]>([])
|
|
||||||
const [pendingEvictions, setPendingEvictions] = createSignal<string[]>([])
|
|
||||||
const sidebarId = `session-sidebar-${props.instance.id}`
|
|
||||||
let previousIsCompact = false
|
|
||||||
|
|
||||||
const shouldShowSidebarToggle = () => isCompactLayout() && !isSidebarOpen()
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
if (typeof window === "undefined") return
|
|
||||||
|
|
||||||
const handleResize = () => {
|
|
||||||
const compact = window.innerWidth < MOBILE_SIDEBAR_BREAKPOINT
|
|
||||||
setIsCompactLayout(compact)
|
|
||||||
if (!compact) {
|
|
||||||
setIsSidebarOpen(true)
|
|
||||||
} else if (!previousIsCompact && compact) {
|
|
||||||
setIsSidebarOpen(false)
|
|
||||||
}
|
|
||||||
previousIsCompact = compact
|
|
||||||
}
|
|
||||||
|
|
||||||
handleResize()
|
|
||||||
window.addEventListener("resize", handleResize)
|
|
||||||
|
|
||||||
onCleanup(() => {
|
|
||||||
window.removeEventListener("resize", handleResize)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const activeSessions = createMemo(() => {
|
|
||||||
const parentId = activeParentSessionId().get(props.instance.id)
|
|
||||||
if (!parentId) return new Map<string, ReturnType<typeof getSessionFamily>[number]>()
|
|
||||||
const sessionFamily = getSessionFamily(props.instance.id, parentId)
|
|
||||||
return new Map(sessionFamily.map((s) => [s.id, s]))
|
|
||||||
})
|
|
||||||
|
|
||||||
const activeSessionIdForInstance = createMemo(() => {
|
|
||||||
return activeSessionMap().get(props.instance.id) || null
|
|
||||||
})
|
|
||||||
|
|
||||||
const parentSessionIdForInstance = createMemo(() => {
|
|
||||||
return activeParentSessionId().get(props.instance.id) || null
|
|
||||||
})
|
|
||||||
|
|
||||||
const activeSessionForInstance = createMemo(() => {
|
|
||||||
const sessionId = activeSessionIdForInstance()
|
|
||||||
if (!sessionId || sessionId === "info") return null
|
|
||||||
return activeSessions().get(sessionId) ?? null
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
const customCommands = createMemo(() => buildCustomCommandEntries(props.instance.id, getInstanceCommands(props.instance.id)))
|
|
||||||
const instancePaletteCommands = createMemo(() => [...props.paletteCommands(), ...customCommands()])
|
|
||||||
const paletteOpen = createMemo(() => isCommandPaletteOpen(props.instance.id))
|
|
||||||
|
|
||||||
const keyboardShortcuts = createMemo(() =>
|
|
||||||
[keyboardRegistry.get("session-prev"), keyboardRegistry.get("session-next")].filter(
|
|
||||||
(shortcut): shortcut is KeyboardShortcut => Boolean(shortcut),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleSessionSelect = (sessionId: string) => {
|
|
||||||
setActiveSession(props.instance.id, sessionId)
|
|
||||||
}
|
|
||||||
|
|
||||||
const evictSession = (sessionId: string) => {
|
|
||||||
if (!sessionId) return
|
|
||||||
log.info("Evicting cached session", { instanceId: props.instance.id, sessionId })
|
|
||||||
const store = messageStoreBus.getInstance(props.instance.id)
|
|
||||||
store?.clearSession(sessionId)
|
|
||||||
clearSessionRenderCache(props.instance.id, sessionId)
|
|
||||||
}
|
|
||||||
|
|
||||||
const scheduleEvictions = (ids: string[]) => {
|
|
||||||
if (!ids.length) return
|
|
||||||
setPendingEvictions((current) => {
|
|
||||||
const existing = new Set(current)
|
|
||||||
const next = [...current]
|
|
||||||
ids.forEach((id) => {
|
|
||||||
if (!existing.has(id)) {
|
|
||||||
next.push(id)
|
|
||||||
existing.add(id)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return next
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
const pending = pendingEvictions()
|
|
||||||
if (!pending.length) return
|
|
||||||
const cached = new Set(cachedSessionIds())
|
|
||||||
const remaining: string[] = []
|
|
||||||
pending.forEach((id) => {
|
|
||||||
if (cached.has(id)) {
|
|
||||||
remaining.push(id)
|
|
||||||
} else {
|
|
||||||
evictSession(id)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (remaining.length !== pending.length) {
|
|
||||||
setPendingEvictions(remaining)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
const sessionsMap = activeSessions()
|
|
||||||
const parentId = parentSessionIdForInstance()
|
|
||||||
const activeId = activeSessionIdForInstance()
|
|
||||||
setCachedSessionIds((current) => {
|
|
||||||
const next: string[] = []
|
|
||||||
const append = (id: string | null) => {
|
|
||||||
if (!id || id === "info") return
|
|
||||||
if (!sessionsMap.has(id)) return
|
|
||||||
if (next.includes(id)) return
|
|
||||||
next.push(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
append(parentId)
|
|
||||||
append(activeId)
|
|
||||||
current.forEach((id) => append(id))
|
|
||||||
|
|
||||||
const limit = parentId ? SESSION_CACHE_LIMIT + 1 : SESSION_CACHE_LIMIT
|
|
||||||
const trimmed = next.length > limit ? next.slice(0, limit) : next
|
|
||||||
const trimmedSet = new Set(trimmed)
|
|
||||||
const removed = current.filter((id) => !trimmedSet.has(id))
|
|
||||||
if (removed.length) {
|
|
||||||
scheduleEvictions(removed)
|
|
||||||
}
|
|
||||||
return trimmed
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Show when={activeSessions().size > 0} fallback={<InstanceWelcomeView instance={props.instance} />}>
|
|
||||||
<div
|
|
||||||
class="flex flex-1 min-h-0 relative"
|
|
||||||
classList={{ "session-layout-compact": isCompactLayout() }}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
id={sidebarId}
|
|
||||||
class="session-sidebar flex flex-col bg-surface-secondary"
|
|
||||||
classList={{
|
|
||||||
"session-sidebar-overlay": isCompactLayout(),
|
|
||||||
"session-sidebar-collapsed": isCompactLayout() && !isSidebarOpen(),
|
|
||||||
}}
|
|
||||||
style={!isCompactLayout() ? { width: `${sessionSidebarWidth()}px` } : undefined}
|
|
||||||
aria-hidden={isCompactLayout() && !isSidebarOpen()}
|
|
||||||
>
|
|
||||||
<SessionList
|
|
||||||
instanceId={props.instance.id}
|
|
||||||
sessions={activeSessions()}
|
|
||||||
activeSessionId={activeSessionIdForInstance()}
|
|
||||||
onSelect={handleSessionSelect}
|
|
||||||
onClose={(id) => {
|
|
||||||
const result = props.onCloseSession(id)
|
|
||||||
if (result instanceof Promise) {
|
|
||||||
void result.catch((error) => log.error("Failed to close session:", error))
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onNew={() => {
|
|
||||||
const result = props.onNewSession()
|
|
||||||
if (result instanceof Promise) {
|
|
||||||
void result.catch((error) => log.error("Failed to create session:", error))
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
showHeader
|
|
||||||
showFooter={false}
|
|
||||||
headerContent={
|
|
||||||
<div class="session-sidebar-header">
|
|
||||||
<div class="session-sidebar-header-row">
|
|
||||||
<span class="session-sidebar-title text-sm font-semibold uppercase text-primary">Sessions</span>
|
|
||||||
<Show when={isCompactLayout()}>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="session-sidebar-close"
|
|
||||||
onClick={() => setIsSidebarOpen(false)}
|
|
||||||
aria-label="Close session sidebar"
|
|
||||||
>
|
|
||||||
Close
|
|
||||||
</button>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
<div class="session-sidebar-shortcuts">
|
|
||||||
{keyboardShortcuts().length ? (
|
|
||||||
<KeyboardHint shortcuts={keyboardShortcuts()} separator=" " showDescription={false} />
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
onWidthChange={setSessionSidebarWidth}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="session-sidebar-separator border-t border-base" />
|
|
||||||
<Show when={activeSessionForInstance()}>
|
|
||||||
{(activeSession) => (
|
|
||||||
<>
|
|
||||||
<ContextUsagePanel instanceId={props.instance.id} sessionId={activeSession().id} />
|
|
||||||
<div class="session-sidebar-controls px-3 py-3 border-r border-base flex flex-col gap-3">
|
|
||||||
<AgentSelector
|
|
||||||
instanceId={props.instance.id}
|
|
||||||
sessionId={activeSession().id}
|
|
||||||
currentAgent={activeSession().agent}
|
|
||||||
onAgentChange={(agent) => props.handleSidebarAgentChange(activeSession().id, agent)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="sidebar-selector-hints" aria-hidden="true">
|
|
||||||
<span class="hint sidebar-selector-hint sidebar-selector-hint--left">
|
|
||||||
<Kbd shortcut="cmd+shift+a" />
|
|
||||||
</span>
|
|
||||||
<span class="hint sidebar-selector-hint sidebar-selector-hint--right">
|
|
||||||
<Kbd shortcut="cmd+shift+m" />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ModelSelector
|
|
||||||
instanceId={props.instance.id}
|
|
||||||
sessionId={activeSession().id}
|
|
||||||
currentModel={activeSession().model}
|
|
||||||
onModelChange={(model) => props.handleSidebarModelChange(activeSession().id, model)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content-area flex-1 min-h-0 overflow-hidden flex flex-col">
|
|
||||||
<Show
|
|
||||||
when={shouldShowSidebarToggle() && (!activeSessionIdForInstance() || activeSessionIdForInstance() === "info")}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="session-sidebar-menu-button session-sidebar-menu-button--floating"
|
|
||||||
onClick={() => setIsSidebarOpen(true)}
|
|
||||||
aria-controls={sidebarId}
|
|
||||||
aria-expanded={isSidebarOpen()}
|
|
||||||
aria-label="Open session list"
|
|
||||||
>
|
|
||||||
<span aria-hidden="true" class="session-sidebar-menu-icon">☰</span>
|
|
||||||
</button>
|
|
||||||
</Show>
|
|
||||||
<Show
|
|
||||||
when={activeSessionIdForInstance() === "info"}
|
|
||||||
fallback={
|
|
||||||
<Show
|
|
||||||
when={cachedSessionIds().length > 0 && activeSessionIdForInstance()}
|
|
||||||
fallback={
|
|
||||||
<div class="flex items-center justify-center h-full">
|
|
||||||
<div class="text-center text-gray-500 dark:text-gray-400">
|
|
||||||
<p class="mb-2">No session selected</p>
|
|
||||||
<p class="text-sm">Select a session to view messages</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<For each={cachedSessionIds()}>
|
|
||||||
{(sessionId) => {
|
|
||||||
const isActive = () => activeSessionIdForInstance() === sessionId
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
class="session-cache-pane flex flex-col flex-1 min-h-0"
|
|
||||||
style={{ display: isActive() ? "flex" : "none" }}
|
|
||||||
data-session-id={sessionId}
|
|
||||||
aria-hidden={!isActive()}
|
|
||||||
>
|
|
||||||
<SessionView
|
|
||||||
sessionId={sessionId}
|
|
||||||
activeSessions={activeSessions()}
|
|
||||||
instanceId={props.instance.id}
|
|
||||||
instanceFolder={props.instance.folder}
|
|
||||||
escapeInDebounce={props.escapeInDebounce}
|
|
||||||
showSidebarToggle={shouldShowSidebarToggle()}
|
|
||||||
onSidebarToggle={() => setIsSidebarOpen(true)}
|
|
||||||
forceCompactStatusLayout={shouldShowSidebarToggle()}
|
|
||||||
isActive={isActive()}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</For>
|
|
||||||
</Show>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<InfoView instanceId={props.instance.id} />
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Show when={isCompactLayout() && isSidebarOpen()}>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="session-sidebar-backdrop"
|
|
||||||
aria-label="Close session sidebar"
|
|
||||||
onClick={() => setIsSidebarOpen(false)}
|
|
||||||
/>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<CommandPalette
|
|
||||||
open={paletteOpen()}
|
|
||||||
onClose={() => hideCommandPalette(props.instance.id)}
|
|
||||||
commands={instancePaletteCommands()}
|
|
||||||
onExecute={props.onExecuteCommand}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default InstanceShell
|
|
||||||
@@ -1,15 +1,11 @@
|
|||||||
import { Show, createEffect, createMemo, createSignal, onCleanup, untrack } from "solid-js"
|
import { Show, createEffect, createMemo, createSignal, onCleanup, untrack } from "solid-js"
|
||||||
import Kbd from "./kbd"
|
import Kbd from "./kbd"
|
||||||
import MessageBlockList, { getMessageAnchorId } from "./message-block-list"
|
import MessageBlockList, { getMessageAnchorId } from "./message-block-list"
|
||||||
import MessageListHeader from "./message-list-header"
|
|
||||||
import MessageTimeline, { buildTimelineSegments, type TimelineSegment } from "./message-timeline"
|
import MessageTimeline, { buildTimelineSegments, type TimelineSegment } from "./message-timeline"
|
||||||
import { useConfig } from "../stores/preferences"
|
import { useConfig } from "../stores/preferences"
|
||||||
import { getSessionInfo } from "../stores/sessions"
|
import { getSessionInfo } from "../stores/sessions"
|
||||||
import { showCommandPalette } from "../stores/command-palette"
|
|
||||||
import { messageStoreBus } from "../stores/message-v2/bus"
|
import { messageStoreBus } from "../stores/message-v2/bus"
|
||||||
import { useScrollCache } from "../lib/hooks/use-scroll-cache"
|
import { useScrollCache } from "../lib/hooks/use-scroll-cache"
|
||||||
import { sseManager } from "../lib/sse-manager"
|
|
||||||
import { formatTokenTotal } from "../lib/formatters"
|
|
||||||
import type { InstanceMessageStore } from "../stores/message-v2/instance-store"
|
import type { InstanceMessageStore } from "../stores/message-v2/instance-store"
|
||||||
|
|
||||||
const SCROLL_SCOPE = "session"
|
const SCROLL_SCOPE = "session"
|
||||||
@@ -19,10 +15,6 @@ const SCROLL_INTENT_KEYS = new Set(["ArrowUp", "ArrowDown", "PageUp", "PageDown"
|
|||||||
const QUOTE_SELECTION_MAX_LENGTH = 2000
|
const QUOTE_SELECTION_MAX_LENGTH = 2000
|
||||||
const codeNomadLogo = new URL("../images/CodeNomad-Icon.png", import.meta.url).href
|
const codeNomadLogo = new URL("../images/CodeNomad-Icon.png", import.meta.url).href
|
||||||
|
|
||||||
function formatTokens(tokens: number): string {
|
|
||||||
return formatTokenTotal(tokens)
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MessageSectionProps {
|
export interface MessageSectionProps {
|
||||||
instanceId: string
|
instanceId: string
|
||||||
sessionId: string
|
sessionId: string
|
||||||
@@ -77,11 +69,6 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
return `${showThinking}|${thinkingExpansion}|${showUsage}`
|
return `${showThinking}|${thinkingExpansion}|${showUsage}`
|
||||||
})
|
})
|
||||||
|
|
||||||
const connectionStatus = () => sseManager.getStatus(props.instanceId)
|
|
||||||
const handleCommandPaletteClick = () => {
|
|
||||||
showCommandPalette(props.instanceId)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleTimelineSegmentClick = (segment: TimelineSegment) => {
|
const handleTimelineSegmentClick = (segment: TimelineSegment) => {
|
||||||
if (typeof document === "undefined") return
|
if (typeof document === "undefined") return
|
||||||
const anchor = document.getElementById(getMessageAnchorId(segment.messageId))
|
const anchor = document.getElementById(getMessageAnchorId(segment.messageId))
|
||||||
@@ -757,17 +744,6 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="message-stream-container">
|
<div class="message-stream-container">
|
||||||
<MessageListHeader
|
|
||||||
usedTokens={tokenStats().used}
|
|
||||||
availableTokens={tokenStats().avail}
|
|
||||||
connectionStatus={connectionStatus()}
|
|
||||||
onCommandPalette={handleCommandPaletteClick}
|
|
||||||
formatTokens={formatTokens}
|
|
||||||
showSidebarToggle={props.showSidebarToggle}
|
|
||||||
onSidebarToggle={props.onSidebarToggle}
|
|
||||||
forceCompactStatusLayout={props.forceCompactStatusLayout}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class={`message-layout${hasTimelineSegments() ? " message-layout--with-timeline" : ""}`}>
|
<div class={`message-layout${hasTimelineSegments() ? " message-layout--with-timeline" : ""}`}>
|
||||||
<div class="message-stream-shell" ref={setShellElement}>
|
<div class="message-stream-shell" ref={setShellElement}>
|
||||||
<div class="message-stream" ref={setContainerRef} onScroll={handleScroll} onMouseUp={handleStreamMouseUp}>
|
<div class="message-stream" ref={setContainerRef} onScroll={handleScroll} onMouseUp={handleStreamMouseUp}>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { createContext, createEffect, createSignal, onMount, useContext, type JSX } from "solid-js"
|
import { createContext, createEffect, createMemo, createSignal, onMount, useContext, type JSX } from "solid-js"
|
||||||
|
import { createTheme, ThemeProvider as MuiThemeProvider } from "@suid/material/styles"
|
||||||
|
import CssBaseline from "@suid/material/CssBaseline"
|
||||||
import { useConfig } from "../stores/preferences"
|
import { useConfig } from "../stores/preferences"
|
||||||
|
|
||||||
interface ThemeContextValue {
|
interface ThemeContextValue {
|
||||||
@@ -10,6 +12,7 @@ interface ThemeContextValue {
|
|||||||
const ThemeContext = createContext<ThemeContextValue>()
|
const ThemeContext = createContext<ThemeContextValue>()
|
||||||
|
|
||||||
function applyTheme(dark: boolean) {
|
function applyTheme(dark: boolean) {
|
||||||
|
if (typeof document === "undefined") return
|
||||||
if (dark) {
|
if (dark) {
|
||||||
document.documentElement.setAttribute("data-theme", "dark")
|
document.documentElement.setAttribute("data-theme", "dark")
|
||||||
return
|
return
|
||||||
@@ -18,8 +21,61 @@ function applyTheme(dark: boolean) {
|
|||||||
document.documentElement.removeAttribute("data-theme")
|
document.documentElement.removeAttribute("data-theme")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ResolvedPaletteColors {
|
||||||
|
backgroundDefault: string
|
||||||
|
backgroundPaper: string
|
||||||
|
primary: string
|
||||||
|
primaryContrast: string
|
||||||
|
textPrimary: string
|
||||||
|
textSecondary: string
|
||||||
|
divider: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const lightPaletteFallbacks: ResolvedPaletteColors = {
|
||||||
|
backgroundDefault: "#ffffff",
|
||||||
|
backgroundPaper: "#f5f5f5",
|
||||||
|
primary: "#0066ff",
|
||||||
|
primaryContrast: "#ffffff",
|
||||||
|
textPrimary: "#1a1a1a",
|
||||||
|
textSecondary: "#666666",
|
||||||
|
divider: "#e0e0e0",
|
||||||
|
}
|
||||||
|
|
||||||
|
const darkPaletteFallbacks: ResolvedPaletteColors = {
|
||||||
|
backgroundDefault: "#1a1a1a",
|
||||||
|
backgroundPaper: "#2a2a2a",
|
||||||
|
primary: "#0080ff",
|
||||||
|
primaryContrast: "#1a1a1a",
|
||||||
|
textPrimary: "#cfd4dc",
|
||||||
|
textSecondary: "#999999",
|
||||||
|
divider: "#3a3a3a",
|
||||||
|
}
|
||||||
|
|
||||||
|
const readCssVar = (token: string, fallback: string, rootStyle: CSSStyleDeclaration | null) => {
|
||||||
|
if (!rootStyle) return fallback
|
||||||
|
const value = rootStyle.getPropertyValue(token)
|
||||||
|
if (!value) return fallback
|
||||||
|
const trimmed = value.trim()
|
||||||
|
return trimmed || fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvePaletteColors = (dark: boolean): ResolvedPaletteColors => {
|
||||||
|
const fallbackSet = dark ? darkPaletteFallbacks : lightPaletteFallbacks
|
||||||
|
const rootStyle = typeof window !== "undefined" ? getComputedStyle(document.documentElement) : null
|
||||||
|
|
||||||
|
return {
|
||||||
|
backgroundDefault: readCssVar("--surface-base", fallbackSet.backgroundDefault, rootStyle),
|
||||||
|
backgroundPaper: readCssVar("--surface-secondary", fallbackSet.backgroundPaper, rootStyle),
|
||||||
|
primary: readCssVar("--accent-primary", fallbackSet.primary, rootStyle),
|
||||||
|
primaryContrast: readCssVar("--text-inverted", fallbackSet.primaryContrast, rootStyle),
|
||||||
|
textPrimary: readCssVar("--text-primary", fallbackSet.textPrimary, rootStyle),
|
||||||
|
textSecondary: readCssVar("--text-secondary", fallbackSet.textSecondary, rootStyle),
|
||||||
|
divider: readCssVar("--border-base", fallbackSet.divider, rootStyle),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function ThemeProvider(props: { children: JSX.Element }) {
|
export function ThemeProvider(props: { children: JSX.Element }) {
|
||||||
const systemPrefersDark = window.matchMedia("(prefers-color-scheme: dark)")
|
const mediaQuery = typeof window !== "undefined" ? window.matchMedia("(prefers-color-scheme: dark)") : null
|
||||||
const { themePreference, setThemePreference } = useConfig()
|
const { themePreference, setThemePreference } = useConfig()
|
||||||
const [isDark, setIsDarkSignal] = createSignal(true)
|
const [isDark, setIsDarkSignal] = createSignal(true)
|
||||||
|
|
||||||
@@ -39,14 +95,15 @@ export function ThemeProvider(props: { children: JSX.Element }) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
if (!mediaQuery) return
|
||||||
const handleSystemThemeChange = () => {
|
const handleSystemThemeChange = () => {
|
||||||
applyResolvedTheme()
|
applyResolvedTheme()
|
||||||
}
|
}
|
||||||
|
|
||||||
systemPrefersDark.addEventListener("change", handleSystemThemeChange)
|
mediaQuery.addEventListener("change", handleSystemThemeChange)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
systemPrefersDark.removeEventListener("change", handleSystemThemeChange)
|
mediaQuery.removeEventListener("change", handleSystemThemeChange)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -58,7 +115,72 @@ export function ThemeProvider(props: { children: JSX.Element }) {
|
|||||||
setTheme(true)
|
setTheme(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ThemeContext.Provider value={{ isDark, toggleTheme, setTheme }}>{props.children}</ThemeContext.Provider>
|
const muiTheme = createMemo(() => {
|
||||||
|
const paletteColors = resolvePaletteColors(isDark())
|
||||||
|
return createTheme({
|
||||||
|
palette: {
|
||||||
|
mode: isDark() ? "dark" : "light",
|
||||||
|
primary: {
|
||||||
|
main: paletteColors.primary,
|
||||||
|
contrastText: paletteColors.primaryContrast,
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
main: paletteColors.primary,
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
default: paletteColors.backgroundDefault,
|
||||||
|
paper: paletteColors.backgroundPaper,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
primary: paletteColors.textPrimary,
|
||||||
|
secondary: paletteColors.textSecondary,
|
||||||
|
},
|
||||||
|
divider: paletteColors.divider,
|
||||||
|
},
|
||||||
|
typography: {
|
||||||
|
fontFamily: "var(--font-family-sans)",
|
||||||
|
},
|
||||||
|
shape: {
|
||||||
|
borderRadius: 8,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
MuiDrawer: {
|
||||||
|
styleOverrides: {
|
||||||
|
paper: {
|
||||||
|
backgroundColor: paletteColors.backgroundPaper,
|
||||||
|
color: paletteColors.textPrimary,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MuiAppBar: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: {
|
||||||
|
backgroundColor: paletteColors.backgroundPaper,
|
||||||
|
color: paletteColors.textPrimary,
|
||||||
|
boxShadow: "none",
|
||||||
|
borderBottom: `1px solid ${paletteColors.divider}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MuiToolbar: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: {
|
||||||
|
minHeight: "56px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as any,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeContext.Provider value={{ isDark, toggleTheme, setTheme }}>
|
||||||
|
<MuiThemeProvider theme={muiTheme()}>
|
||||||
|
<CssBaseline />
|
||||||
|
{props.children}
|
||||||
|
</MuiThemeProvider>
|
||||||
|
</ThemeContext.Provider>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTheme() {
|
export function useTheme() {
|
||||||
|
|||||||
Reference in New Issue
Block a user