tui: add session search functionality with debounced input and server-side filtering

This commit is contained in:
Dax Raad
2026-01-04 13:38:30 -05:00
parent cdd6ea514b
commit 7304ba616e
9 changed files with 79 additions and 12 deletions

View File

@@ -2,13 +2,14 @@ import { useDialog } from "@tui/ui/dialog"
import { DialogSelect } from "@tui/ui/dialog-select"
import { useRoute } from "@tui/context/route"
import { useSync } from "@tui/context/sync"
import { createEffect, createMemo, createSignal, onMount, Show } from "solid-js"
import { createMemo, createSignal, createResource, onMount, Show } from "solid-js"
import { Locale } from "@/util/locale"
import { Keybind } from "@/util/keybind"
import { useTheme } from "../context/theme"
import { useSDK } from "../context/sdk"
import { DialogSessionRename } from "./dialog-session-rename"
import { useKV } from "../context/kv"
import { createDebouncedSignal } from "../util/signal"
import "opentui-spinner/solid"
export function DialogSessionList() {
@@ -20,6 +21,13 @@ export function DialogSessionList() {
const kv = useKV()
const [toDelete, setToDelete] = createSignal<string>()
const [search, setSearch] = createDebouncedSignal("", 150)
const [searchResults] = createResource(search, async (query) => {
if (!query) return undefined
const result = await sdk.client.session.list({ search: query, limit: 30 })
return result.data ?? []
})
const deleteKeybind = "ctrl+d"
@@ -27,9 +35,11 @@ export function DialogSessionList() {
const spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
const sessions = createMemo(() => searchResults() ?? sync.data.session)
const options = createMemo(() => {
const today = new Date().toDateString()
return sync.data.session
return sessions()
.filter((x) => x.parentID === undefined)
.toSorted((a, b) => b.time.updated - a.time.updated)
.map((x) => {
@@ -54,11 +64,6 @@ export function DialogSessionList() {
) : undefined,
}
})
.slice(0, 150)
})
createEffect(() => {
console.log("session count", sync.data.session.length)
})
onMount(() => {
@@ -69,7 +74,9 @@ export function DialogSessionList() {
<DialogSelect
title="Sessions"
options={options()}
skipFilter={true}
current={currentSessionID()}
onFilter={setSearch}
onMove={() => {
setToDelete(undefined)
}}

View File

@@ -269,8 +269,9 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
async function bootstrap() {
console.log("bootstrapping")
const start = Date.now() - 30 * 24 * 60 * 60 * 1000
const sessionListPromise = sdk.client.session
.list()
.list({ start: start })
.then((x) => setStore("session", reconcile((x.data ?? []).toSorted((a, b) => a.id.localeCompare(b.id)))))
// blocking - include session.list when continuing a session

View File

@@ -71,12 +71,14 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
let input: InputRenderable
const filtered = createMemo(() => {
if (props.skipFilter) {
return props.options.filter((x) => x.disabled !== true)
}
const needle = store.filter.toLowerCase()
const result = pipe(
props.options,
filter((x) => x.disabled !== true),
(x) =>
!needle || props.skipFilter ? x : fuzzysort.go(needle, x, { keys: ["title", "category"] }).map((x) => x.obj),
(x) => (!needle ? x : fuzzysort.go(needle, x, { keys: ["title", "category"] }).map((x) => x.obj)),
)
return result
})

View File

@@ -0,0 +1,7 @@
import { createSignal, type Accessor } from "solid-js"
import { debounce, type Scheduled } from "@solid-primitives/scheduled"
export function createDebouncedSignal<T>(value: T, ms: number): [Accessor<T>, Scheduled<[value: T]>] {
const [get, set] = createSignal(value)
return [get, debounce((v: T) => set(() => v), ms)]
}