mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-13 09:01:54 +00:00
Hold (~450ms) a profile square — or right-click → Color… — to open a shadcn Popover of swatches and override its rail color, with Auto to fall back to the deterministic hue. The hold timer rides alongside the dnd pointer listener (a real drag cancels it, the trailing click is suppressed), so reorder/select/recolor stay distinct gestures. Overrides persist in localStorage ($profileColors), resolved via resolveProfileColor (override wins, else the name-hashed hue). Cosmetic and gated on the multi-profile rail, so single-profile users are unaffected. Adds a reusable ui/popover.tsx (radix-ui umbrella).
107 lines
2.5 KiB
TypeScript
107 lines
2.5 KiB
TypeScript
export function storedBoolean(key: string, fallback: boolean): boolean {
|
|
try {
|
|
const value = window.localStorage.getItem(key)
|
|
|
|
return value === null ? fallback : value === 'true'
|
|
} catch {
|
|
return fallback
|
|
}
|
|
}
|
|
|
|
export function persistBoolean(key: string, value: boolean) {
|
|
try {
|
|
window.localStorage.setItem(key, String(value))
|
|
} catch {
|
|
// Local storage is a convenience; ignore failures in restricted contexts.
|
|
}
|
|
}
|
|
|
|
export function storedString(key: string): null | string {
|
|
try {
|
|
return window.localStorage.getItem(key)
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
export function persistString(key: string, value: null | string) {
|
|
try {
|
|
if (value === null) {
|
|
window.localStorage.removeItem(key)
|
|
} else {
|
|
window.localStorage.setItem(key, value)
|
|
}
|
|
} catch {
|
|
// Storage is best-effort.
|
|
}
|
|
}
|
|
|
|
export function storedStringArray(key: string): string[] {
|
|
try {
|
|
const value = window.localStorage.getItem(key)
|
|
|
|
if (!value) {
|
|
return []
|
|
}
|
|
|
|
const parsed = JSON.parse(value)
|
|
|
|
if (!Array.isArray(parsed)) {
|
|
return []
|
|
}
|
|
|
|
return parsed.filter((item): item is string => typeof item === 'string' && item.length > 0)
|
|
} catch {
|
|
return []
|
|
}
|
|
}
|
|
|
|
export function persistStringArray(key: string, value: string[]) {
|
|
try {
|
|
window.localStorage.setItem(key, JSON.stringify(value))
|
|
} catch {
|
|
// Pins are a local preference; restricted storage should not break chat.
|
|
}
|
|
}
|
|
|
|
export function storedStringRecord(key: string): Record<string, string> {
|
|
try {
|
|
const value = window.localStorage.getItem(key)
|
|
|
|
if (!value) {
|
|
return {}
|
|
}
|
|
|
|
const parsed = JSON.parse(value)
|
|
|
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
return {}
|
|
}
|
|
|
|
return Object.fromEntries(
|
|
Object.entries(parsed).filter((entry): entry is [string, string] => typeof entry[1] === 'string')
|
|
)
|
|
} catch {
|
|
return {}
|
|
}
|
|
}
|
|
|
|
export function persistStringRecord(key: string, value: Record<string, string>) {
|
|
try {
|
|
window.localStorage.setItem(key, JSON.stringify(value))
|
|
} catch {
|
|
// Local preference; restricted storage should not break the app.
|
|
}
|
|
}
|
|
|
|
export function arraysEqual(left: string[], right: string[]) {
|
|
return left.length === right.length && left.every((item, index) => item === right[index])
|
|
}
|
|
|
|
export function insertUniqueId(ids: string[], id: string, index: number) {
|
|
const next = ids.filter(item => item !== id)
|
|
const boundedIndex = Math.min(Math.max(index, 0), next.length)
|
|
next.splice(boundedIndex, 0, id)
|
|
|
|
return next
|
|
}
|