现在会话输入框会记住用户的配置选项和模型选择,避免每次都要重新勾选
This commit is contained in:
221
src/lib/selector-prefs-storage.ts
Normal file
221
src/lib/selector-prefs-storage.ts
Normal file
@@ -0,0 +1,221 @@
|
||||
"use client"
|
||||
|
||||
/**
|
||||
* Persists user's selector preferences (mode & config option selections)
|
||||
* per agentType to localStorage, so they survive session restarts.
|
||||
*
|
||||
* Structure hash is stored alongside values — when the available options
|
||||
* change (new/removed/renamed items) the saved prefs are discarded.
|
||||
*
|
||||
* Agents may emit empty selectors during early init (config_options=None
|
||||
* becomes []), followed by real selectors later. We therefore only apply
|
||||
* or invalidate prefs when incoming data is non-empty. Stale prefs for
|
||||
* agents that genuinely lose all selectors are cleaned up at
|
||||
* `selectors_ready` via `clearStalePrefs()`.
|
||||
*/
|
||||
|
||||
import type { SessionConfigOptionInfo, SessionModeStateInfo } from "@/lib/types"
|
||||
|
||||
const STORAGE_KEY = "codeg:selector-prefs"
|
||||
|
||||
interface SelectorPrefs {
|
||||
modeId?: string
|
||||
modesHash?: string
|
||||
configValues?: Record<string, string>
|
||||
configHash?: string
|
||||
}
|
||||
|
||||
type AllPrefs = Record<string, SelectorPrefs>
|
||||
|
||||
function readAll(): AllPrefs {
|
||||
if (typeof window === "undefined") return {}
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY)
|
||||
return raw ? (JSON.parse(raw) as AllPrefs) : {}
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
function writeAll(all: AllPrefs) {
|
||||
if (typeof window === "undefined") return
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(all))
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function updatePrefs(
|
||||
agentType: string,
|
||||
fn: (prefs: SelectorPrefs) => SelectorPrefs
|
||||
) {
|
||||
const all = readAll()
|
||||
all[agentType] = fn(all[agentType] ?? {})
|
||||
writeAll(all)
|
||||
}
|
||||
|
||||
// ── Hash helpers ──
|
||||
|
||||
function hashModes(modes: SessionModeStateInfo): string {
|
||||
return modes.available_modes.map((m) => m.id).join("\0")
|
||||
}
|
||||
|
||||
function hashConfigOptions(options: SessionConfigOptionInfo[]): string {
|
||||
return options
|
||||
.map((o) => {
|
||||
if (o.kind.type !== "select") return o.id
|
||||
const vals = o.kind.options.map((v) => v.value).join(",")
|
||||
return `${o.id}:${vals}`
|
||||
})
|
||||
.join("\0")
|
||||
}
|
||||
|
||||
// ── Read ──
|
||||
|
||||
/** Read saved mode id for an agent (no validation, just the raw value). */
|
||||
export function getSavedModeId(agentType: string): string | null {
|
||||
const all = readAll()
|
||||
return all[agentType]?.modeId ?? null
|
||||
}
|
||||
|
||||
// ── Save (user actions only) ──
|
||||
|
||||
export function saveModePreference(
|
||||
agentType: string,
|
||||
modes: SessionModeStateInfo
|
||||
) {
|
||||
updatePrefs(agentType, (prefs) => ({
|
||||
...prefs,
|
||||
modeId: modes.current_mode_id,
|
||||
modesHash: hashModes(modes),
|
||||
}))
|
||||
}
|
||||
|
||||
export function saveConfigPreference(
|
||||
agentType: string,
|
||||
configId: string,
|
||||
valueId: string,
|
||||
allOptions: SessionConfigOptionInfo[]
|
||||
) {
|
||||
updatePrefs(agentType, (prefs) => ({
|
||||
...prefs,
|
||||
configValues: { ...prefs.configValues, [configId]: valueId },
|
||||
configHash: hashConfigOptions(allOptions),
|
||||
}))
|
||||
}
|
||||
|
||||
// ── Apply (on incoming server events) ──
|
||||
|
||||
/**
|
||||
* Apply saved mode preference to incoming server modes.
|
||||
* Skips empty mode lists (agent still initializing).
|
||||
* Clears prefs when structure genuinely changes.
|
||||
*/
|
||||
export function applySavedModePreference(
|
||||
agentType: string,
|
||||
modes: SessionModeStateInfo
|
||||
): SessionModeStateInfo {
|
||||
const all = readAll()
|
||||
const prefs = all[agentType]
|
||||
if (!prefs?.modeId || !prefs.modesHash) return modes
|
||||
if (modes.available_modes.length === 0) return modes
|
||||
|
||||
const incomingHash = hashModes(modes)
|
||||
if (prefs.modesHash !== incomingHash) {
|
||||
delete prefs.modeId
|
||||
delete prefs.modesHash
|
||||
all[agentType] = prefs
|
||||
writeAll(all)
|
||||
return modes
|
||||
}
|
||||
|
||||
if (!modes.available_modes.some((m) => m.id === prefs.modeId)) {
|
||||
return modes
|
||||
}
|
||||
|
||||
if (modes.current_mode_id === prefs.modeId) return modes
|
||||
|
||||
return { ...modes, current_mode_id: prefs.modeId! }
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply saved config option preferences to incoming server config options.
|
||||
* Skips empty option lists (agent still initializing).
|
||||
* Clears prefs when structure genuinely changes.
|
||||
*/
|
||||
export function applySavedConfigPreferences(
|
||||
agentType: string,
|
||||
options: SessionConfigOptionInfo[]
|
||||
): SessionConfigOptionInfo[] {
|
||||
const all = readAll()
|
||||
const prefs = all[agentType]
|
||||
if (!prefs?.configValues || !prefs.configHash) return options
|
||||
if (options.length === 0) return options
|
||||
|
||||
const incomingHash = hashConfigOptions(options)
|
||||
if (prefs.configHash !== incomingHash) {
|
||||
delete prefs.configValues
|
||||
delete prefs.configHash
|
||||
all[agentType] = prefs
|
||||
writeAll(all)
|
||||
return options
|
||||
}
|
||||
|
||||
let changed = false
|
||||
const merged = options.map((opt) => {
|
||||
if (opt.kind.type !== "select") return opt
|
||||
const savedValue = prefs.configValues![opt.id]
|
||||
if (!savedValue || savedValue === opt.kind.current_value) return opt
|
||||
if (!opt.kind.options.some((o) => o.value === savedValue)) return opt
|
||||
changed = true
|
||||
return {
|
||||
...opt,
|
||||
kind: { ...opt.kind, current_value: savedValue },
|
||||
}
|
||||
})
|
||||
|
||||
return changed ? merged : options
|
||||
}
|
||||
|
||||
// ── Cleanup (called at selectors_ready) ──
|
||||
|
||||
/**
|
||||
* Called when `selectors_ready` fires — initialization is complete.
|
||||
* If the agent ended up with no modes / no config options, clear
|
||||
* any stale saved prefs for that category so they don't linger forever.
|
||||
*/
|
||||
export function clearStalePrefs(
|
||||
agentType: string,
|
||||
hasModes: boolean,
|
||||
hasConfigOptions: boolean
|
||||
) {
|
||||
const all = readAll()
|
||||
const prefs = all[agentType]
|
||||
if (!prefs) return
|
||||
|
||||
let dirty = false
|
||||
if (!hasModes && (prefs.modeId || prefs.modesHash)) {
|
||||
delete prefs.modeId
|
||||
delete prefs.modesHash
|
||||
dirty = true
|
||||
}
|
||||
if (!hasConfigOptions && (prefs.configValues || prefs.configHash)) {
|
||||
delete prefs.configValues
|
||||
delete prefs.configHash
|
||||
dirty = true
|
||||
}
|
||||
if (!dirty) return
|
||||
|
||||
const isEmpty =
|
||||
!prefs.modeId &&
|
||||
!prefs.modesHash &&
|
||||
!prefs.configValues &&
|
||||
!prefs.configHash
|
||||
if (isEmpty) {
|
||||
delete all[agentType]
|
||||
} else {
|
||||
all[agentType] = prefs
|
||||
}
|
||||
writeAll(all)
|
||||
}
|
||||
Reference in New Issue
Block a user