@bojackduy/opencode-telescope 0.1.15 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/search.ts +18 -74
- package/telescope.tsx +46 -97
- package/ui/render-target.ts +0 -2
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@bojackduy/opencode-telescope",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.16",
|
|
5
5
|
"description": "Fuzzy search across all OpenCode conversations — grep session and chat history, find code snippets, and jump to any chat instantly",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
package/search.ts
CHANGED
|
@@ -11,7 +11,6 @@ export type SearchResult = {
|
|
|
11
11
|
sessionTitle: string
|
|
12
12
|
directory: string
|
|
13
13
|
role: "user" | "assistant"
|
|
14
|
-
partType: "text" | "reasoning" | "tool" | "patch"
|
|
15
14
|
timeCreated: number
|
|
16
15
|
snippet: string
|
|
17
16
|
matchStart: number
|
|
@@ -68,15 +67,10 @@ type Row = {
|
|
|
68
67
|
session_title: string | null
|
|
69
68
|
directory: string
|
|
70
69
|
role: SearchRole
|
|
71
|
-
part_type?: SearchResult["partType"]
|
|
72
70
|
time_created: number
|
|
73
71
|
text: string
|
|
74
72
|
}
|
|
75
73
|
|
|
76
|
-
type SourceRow = Omit<Row, "text"> & {
|
|
77
|
-
data: string
|
|
78
|
-
}
|
|
79
|
-
|
|
80
74
|
type ConversationRow = {
|
|
81
75
|
id: string
|
|
82
76
|
message_id: string
|
|
@@ -324,7 +318,6 @@ export function rowToSearchResult(row: Row, query: string): SearchResult | undef
|
|
|
324
318
|
sessionTitle: row.session_title || "Untitled session",
|
|
325
319
|
directory: row.directory,
|
|
326
320
|
role: row.role,
|
|
327
|
-
partType: row.part_type ?? "text",
|
|
328
321
|
timeCreated: row.time_created,
|
|
329
322
|
snippet: makeSnippet(text, query),
|
|
330
323
|
matchStart: match.start,
|
|
@@ -344,7 +337,7 @@ export function rowToSearchResult(row: Row, query: string): SearchResult | undef
|
|
|
344
337
|
|
|
345
338
|
export function extractSearchText(data: string) {
|
|
346
339
|
try {
|
|
347
|
-
return
|
|
340
|
+
return extractFromValue(JSON.parse(data)).replace(/\s+/g, " ").trim()
|
|
348
341
|
} catch {
|
|
349
342
|
return data.replace(/\s+/g, " ").trim()
|
|
350
343
|
}
|
|
@@ -576,7 +569,7 @@ function indexedTextRows(db: Database, dbPath: string, limit: number, query: str
|
|
|
576
569
|
debug.time("query:fts:exec")
|
|
577
570
|
const rows = index.query<Row, (string | number)[]>(`
|
|
578
571
|
SELECT id, message_id, session_id, session_title, directory, role,
|
|
579
|
-
|
|
572
|
+
CAST(time_created AS INTEGER) AS time_created, text
|
|
580
573
|
FROM document_fts
|
|
581
574
|
WHERE ${conditions.join(" AND ")}
|
|
582
575
|
ORDER BY bm25(document_fts), CAST(time_created AS INTEGER) DESC
|
|
@@ -593,7 +586,7 @@ function indexedTextRows(db: Database, dbPath: string, limit: number, query: str
|
|
|
593
586
|
function visibleTextRows(db: Database, limit: number, query?: string, directory?: string, offset?: number, role?: SearchRole) {
|
|
594
587
|
const offsetClause = offset ? "OFFSET ?" : ""
|
|
595
588
|
const conditions: string[] = [
|
|
596
|
-
"json_extract(p.data, '$.type')
|
|
589
|
+
"json_extract(p.data, '$.type') = 'text'",
|
|
597
590
|
role ? "json_extract(m.data, '$.role') = ?" : "json_extract(m.data, '$.role') IN ('user', 'assistant')",
|
|
598
591
|
]
|
|
599
592
|
const params: (string | number)[] = []
|
|
@@ -607,7 +600,7 @@ function visibleTextRows(db: Database, limit: number, query?: string, directory?
|
|
|
607
600
|
|
|
608
601
|
const tokens = query ? query.trim().split(/\s+/).filter(Boolean) : []
|
|
609
602
|
for (const token of tokens) {
|
|
610
|
-
conditions.push("p.data LIKE ?")
|
|
603
|
+
conditions.push("json_extract(p.data, '$.text') LIKE ?")
|
|
611
604
|
params.push(`%${token}%`)
|
|
612
605
|
}
|
|
613
606
|
|
|
@@ -617,9 +610,8 @@ function visibleTextRows(db: Database, limit: number, query?: string, directory?
|
|
|
617
610
|
const sql = `
|
|
618
611
|
SELECT p.id, p.message_id, p.session_id, s.title AS session_title, s.directory,
|
|
619
612
|
json_extract(m.data, '$.role') AS role,
|
|
620
|
-
json_extract(p.data, '$.type') AS part_type,
|
|
621
613
|
p.time_created,
|
|
622
|
-
p.data
|
|
614
|
+
json_extract(p.data, '$.text') AS text
|
|
623
615
|
FROM part p
|
|
624
616
|
JOIN message m ON m.id = p.message_id
|
|
625
617
|
JOIN session s ON s.id = p.session_id
|
|
@@ -628,9 +620,9 @@ function visibleTextRows(db: Database, limit: number, query?: string, directory?
|
|
|
628
620
|
LIMIT ? ${offsetClause}
|
|
629
621
|
`
|
|
630
622
|
debug.time("query:sql:exec")
|
|
631
|
-
const rows = db.query<
|
|
623
|
+
const rows = db.query<Row, (string | number)[]>(sql).all(...params as any[])
|
|
632
624
|
debug.timeEnd("query:sql:exec")
|
|
633
|
-
return rows
|
|
625
|
+
return rows
|
|
634
626
|
}
|
|
635
627
|
|
|
636
628
|
function ensureSearchIndex(source: Database, sourcePath: string) {
|
|
@@ -646,15 +638,12 @@ function ensureSearchIndex(source: Database, sourcePath: string) {
|
|
|
646
638
|
const currentDataVersion = getMeta(_indexDb, "source_data_version")
|
|
647
639
|
const currentMtimeMs = getMeta(_indexDb, "source_mtime_ms")
|
|
648
640
|
const currentPath = getMeta(_indexDb, "source_path")
|
|
649
|
-
|
|
650
|
-
if (currentPath !== sourcePath || currentDataVersion !== String(state.dataVersion) || currentMtimeMs !== String(state.mtimeMs) || currentIndexVersion !== SEARCH_INDEX_VERSION) {
|
|
641
|
+
if (currentPath !== sourcePath || currentDataVersion !== String(state.dataVersion) || currentMtimeMs !== String(state.mtimeMs)) {
|
|
651
642
|
rebuildSearchIndex(source, _indexDb, sourcePath, state)
|
|
652
643
|
}
|
|
653
644
|
return _indexDb
|
|
654
645
|
}
|
|
655
646
|
|
|
656
|
-
const SEARCH_INDEX_VERSION = "3"
|
|
657
|
-
|
|
658
647
|
function searchIndexPath(sourcePath: string) {
|
|
659
648
|
const parsed = path.parse(sourcePath)
|
|
660
649
|
return path.join(parsed.dir, `${parsed.name}-search.db`)
|
|
@@ -673,31 +662,11 @@ function migrateSearchIndex(db: Database) {
|
|
|
673
662
|
session_title,
|
|
674
663
|
directory UNINDEXED,
|
|
675
664
|
role UNINDEXED,
|
|
676
|
-
part_type UNINDEXED,
|
|
677
665
|
time_created UNINDEXED,
|
|
678
666
|
text,
|
|
679
667
|
tokenize='unicode61'
|
|
680
668
|
);
|
|
681
669
|
`)
|
|
682
|
-
|
|
683
|
-
const hasPartType = db.query<{ name: string }, []>("PRAGMA table_info(document_fts)").all().some((column) => column.name === "part_type")
|
|
684
|
-
if (!hasPartType) {
|
|
685
|
-
db.exec("DROP TABLE document_fts")
|
|
686
|
-
db.exec(`
|
|
687
|
-
CREATE VIRTUAL TABLE document_fts USING fts5(
|
|
688
|
-
id UNINDEXED,
|
|
689
|
-
message_id UNINDEXED,
|
|
690
|
-
session_id UNINDEXED,
|
|
691
|
-
session_title,
|
|
692
|
-
directory UNINDEXED,
|
|
693
|
-
role UNINDEXED,
|
|
694
|
-
part_type UNINDEXED,
|
|
695
|
-
time_created UNINDEXED,
|
|
696
|
-
text,
|
|
697
|
-
tokenize='unicode61'
|
|
698
|
-
);
|
|
699
|
-
`)
|
|
700
|
-
}
|
|
701
670
|
}
|
|
702
671
|
|
|
703
672
|
function sourceState(db: Database, sourcePath: string) {
|
|
@@ -708,27 +677,27 @@ function sourceState(db: Database, sourcePath: string) {
|
|
|
708
677
|
|
|
709
678
|
function rebuildSearchIndex(source: Database, index: Database, sourcePath: string, state: { dataVersion: number; mtimeMs: number }) {
|
|
710
679
|
debug.time("fts:rebuild")
|
|
711
|
-
const rows = source.query<
|
|
680
|
+
const rows = source.query<Row, []>(`
|
|
712
681
|
SELECT p.id, p.message_id, p.session_id, s.title AS session_title, s.directory,
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
p.data
|
|
682
|
+
json_extract(m.data, '$.role') AS role,
|
|
683
|
+
p.time_created,
|
|
684
|
+
json_extract(p.data, '$.text') AS text
|
|
717
685
|
FROM part p
|
|
718
686
|
JOIN message m ON m.id = p.message_id
|
|
719
687
|
JOIN session s ON s.id = p.session_id
|
|
720
|
-
WHERE json_extract(p.data, '$.type')
|
|
688
|
+
WHERE json_extract(p.data, '$.type') = 'text'
|
|
721
689
|
AND json_extract(m.data, '$.role') IN ('user', 'assistant')
|
|
690
|
+
AND json_extract(p.data, '$.text') IS NOT NULL
|
|
722
691
|
ORDER BY p.time_created DESC
|
|
723
692
|
`).all()
|
|
724
|
-
const insert = index.query<Row, [string, string, string, string, string, string,
|
|
725
|
-
INSERT INTO document_fts(id, message_id, session_id, session_title, directory, role,
|
|
726
|
-
VALUES (?, ?, ?, ?, ?, ?, ?,
|
|
693
|
+
const insert = index.query<Row, [string, string, string, string, string, string, number, string]>(`
|
|
694
|
+
INSERT INTO document_fts(id, message_id, session_id, session_title, directory, role, time_created, text)
|
|
695
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
727
696
|
`)
|
|
728
697
|
index.exec("BEGIN IMMEDIATE")
|
|
729
698
|
try {
|
|
730
699
|
index.exec("DELETE FROM document_fts")
|
|
731
|
-
for (const row of rows
|
|
700
|
+
for (const row of rows) {
|
|
732
701
|
insert.run(
|
|
733
702
|
row.id,
|
|
734
703
|
row.message_id,
|
|
@@ -736,7 +705,6 @@ function rebuildSearchIndex(source: Database, index: Database, sourcePath: strin
|
|
|
736
705
|
row.session_title ?? "Untitled session",
|
|
737
706
|
row.directory,
|
|
738
707
|
row.role,
|
|
739
|
-
row.part_type ?? "text",
|
|
740
708
|
row.time_created,
|
|
741
709
|
row.text,
|
|
742
710
|
)
|
|
@@ -744,7 +712,6 @@ function rebuildSearchIndex(source: Database, index: Database, sourcePath: strin
|
|
|
744
712
|
setMeta(index, "source_path", sourcePath)
|
|
745
713
|
setMeta(index, "source_data_version", String(state.dataVersion))
|
|
746
714
|
setMeta(index, "source_mtime_ms", String(state.mtimeMs))
|
|
747
|
-
setMeta(index, "index_version", SEARCH_INDEX_VERSION)
|
|
748
715
|
index.exec("COMMIT")
|
|
749
716
|
} catch (err) {
|
|
750
717
|
index.exec("ROLLBACK")
|
|
@@ -816,29 +783,6 @@ function extractFromValue(value: unknown): string {
|
|
|
816
783
|
.join("\n")
|
|
817
784
|
}
|
|
818
785
|
|
|
819
|
-
function sourceRowToSearchRow(row: SourceRow): Row[] {
|
|
820
|
-
const text = extractSearchText(row.data)
|
|
821
|
-
if (!text) return []
|
|
822
|
-
return [{ ...row, text }]
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
function searchablePartText(value: unknown): string {
|
|
826
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return extractFromValue(value)
|
|
827
|
-
const record = value as Record<string, unknown>
|
|
828
|
-
const type = record.type
|
|
829
|
-
|
|
830
|
-
if (type === "text" || type === "reasoning") return typeof record.text === "string" ? record.text : extractFromValue(record)
|
|
831
|
-
if (type === "patch") return extractFromValue(record.files)
|
|
832
|
-
|
|
833
|
-
if (type === "tool") {
|
|
834
|
-
const state = record.state && typeof record.state === "object" && !Array.isArray(record.state) ? record.state as Record<string, unknown> : undefined
|
|
835
|
-
if (record.tool !== "apply_patch") return ""
|
|
836
|
-
return extractFromValue(state?.input)
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
return extractFromValue(record)
|
|
840
|
-
}
|
|
841
|
-
|
|
842
786
|
function truncate(value: string, length: number) {
|
|
843
787
|
if (value.length <= length) return value
|
|
844
788
|
return `${value.slice(0, length - 3)}...`
|
package/telescope.tsx
CHANGED
|
@@ -46,14 +46,14 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
46
46
|
const [loadingPreviewMore, setLoadingPreviewMore] = createSignal(false)
|
|
47
47
|
const [prefetchingPreviewBefore, setPrefetchingPreviewBefore] = createSignal(false)
|
|
48
48
|
const [prefetchingPreviewAfter, setPrefetchingPreviewAfter] = createSignal(false)
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
const RESULT_PREFETCH_AHEAD_ROWS = 25
|
|
49
|
+
const MIN_SEARCH_BATCH_SIZE = 25
|
|
50
|
+
const MIN_RECENT_BATCH_SIZE = 15
|
|
52
51
|
const RESULT_OVERSCAN_MULTIPLIER = 2
|
|
52
|
+
const RESULT_BATCH_VIEWPORTS = 4
|
|
53
|
+
const RESULT_PREFETCH_VIEWPORTS = 10
|
|
53
54
|
const RESULT_CACHE_BEHIND_VIEWPORTS = 6
|
|
54
|
-
const INITIAL_PREVIEW_BEFORE =
|
|
55
|
-
const INITIAL_PREVIEW_AFTER =
|
|
56
|
-
const INITIAL_PREVIEW_DELAY_MS = 75
|
|
55
|
+
const INITIAL_PREVIEW_BEFORE = 20
|
|
56
|
+
const INITIAL_PREVIEW_AFTER = 30
|
|
57
57
|
const PREVIEW_PAGE_SIZE = 20
|
|
58
58
|
const PREVIEW_PREFETCH_VIEWPORTS = 0.5
|
|
59
59
|
let input: InputRenderable | undefined
|
|
@@ -91,19 +91,18 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
91
91
|
let resultPreviousTimer: ReturnType<typeof setTimeout> | undefined
|
|
92
92
|
let previewBeforeTimer: ReturnType<typeof setTimeout> | undefined
|
|
93
93
|
let previewAfterTimer: ReturnType<typeof setTimeout> | undefined
|
|
94
|
-
let pendingPreviewBefore: {
|
|
95
|
-
let
|
|
94
|
+
let pendingPreviewBefore: { previousContentHeight: number; preserveScroll: boolean; visibleLoad: boolean } | undefined
|
|
95
|
+
let pendingPreviewAfterVisible = false
|
|
96
96
|
const cancelPreviewPrefetch = () => {
|
|
97
97
|
if (previewBeforeTimer) clearTimeout(previewBeforeTimer)
|
|
98
98
|
if (previewAfterTimer) clearTimeout(previewAfterTimer)
|
|
99
99
|
previewBeforeTimer = undefined
|
|
100
100
|
previewAfterTimer = undefined
|
|
101
101
|
pendingPreviewBefore = undefined
|
|
102
|
-
|
|
102
|
+
pendingPreviewAfterVisible = false
|
|
103
103
|
setPrefetchingPreviewBefore(false)
|
|
104
104
|
setPrefetchingPreviewAfter(false)
|
|
105
105
|
setLoadingPreviewMore(false)
|
|
106
|
-
setPreviewEdgeLoadingReady(false)
|
|
107
106
|
}
|
|
108
107
|
onCleanup(() => {
|
|
109
108
|
if (resultPrefetchTimer) clearTimeout(resultPrefetchTimer)
|
|
@@ -116,9 +115,9 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
116
115
|
const viewportHeight = resultScroll?.height || Math.max(8, height() - 7)
|
|
117
116
|
return Math.max(5, Math.floor(viewportHeight / resultRowHeight()))
|
|
118
117
|
}
|
|
119
|
-
const searchBatchSize = () =>
|
|
120
|
-
const recentBatchSize = () =>
|
|
121
|
-
const resultPrefetchThreshold = () =>
|
|
118
|
+
const searchBatchSize = () => Math.max(MIN_SEARCH_BATCH_SIZE, visibleResultRows() * RESULT_BATCH_VIEWPORTS)
|
|
119
|
+
const recentBatchSize = () => Math.max(MIN_RECENT_BATCH_SIZE, visibleResultRows() * RESULT_BATCH_VIEWPORTS)
|
|
120
|
+
const resultPrefetchThreshold = () => visibleResultRows() * RESULT_PREFETCH_VIEWPORTS
|
|
122
121
|
const resultPrefetchState = () => {
|
|
123
122
|
const cachedEnd = resultBaseOffset() + results().length
|
|
124
123
|
const rowsAhead = Math.max(0, cachedEnd - selected() - 1)
|
|
@@ -438,20 +437,14 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
438
437
|
return lastChild ? lastChild.y + lastChild.height : 0
|
|
439
438
|
}
|
|
440
439
|
|
|
441
|
-
const
|
|
442
|
-
if (!previewScroll || previewScroll.y === 0) return
|
|
443
|
-
previewScroll.scrollBy(-previewScroll.y)
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
const loadPreviewBefore = (itemId: string, previousContentHeight = previewContentHeight(), preserveScroll = true, visibleLoad = false) => {
|
|
440
|
+
const loadPreviewBefore = (previousContentHeight = previewContentHeight(), preserveScroll = true, visibleLoad = false) => {
|
|
447
441
|
const item = selectedResult()
|
|
448
442
|
const first = previewParts()[0]
|
|
449
|
-
if (!item ||
|
|
443
|
+
if (!item || !first || loadingPreviewMore() || prefetchingPreviewBefore()) return
|
|
450
444
|
visibleLoad ? setLoadingPreviewMore(true) : setPrefetchingPreviewBefore(true)
|
|
451
445
|
debug.time("preview:load-before")
|
|
452
446
|
try {
|
|
453
447
|
const page = loadConversationBefore(item, { id: first.id, timeCreated: first.timeCreated }, { limit: PREVIEW_PAGE_SIZE, dbPath: dbPath() })
|
|
454
|
-
if (selectedResult()?.id !== itemId) return
|
|
455
448
|
debug.log("preview:load-before", {
|
|
456
449
|
item: item.id,
|
|
457
450
|
added: page.parts.length,
|
|
@@ -465,7 +458,6 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
465
458
|
setPreviewParts((prev) => [...page.parts, ...prev])
|
|
466
459
|
if (preserveScroll) {
|
|
467
460
|
setTimeout(() => {
|
|
468
|
-
if (selectedResult()?.id !== itemId) return
|
|
469
461
|
const delta = previewContentHeight() - previousContentHeight
|
|
470
462
|
if (delta > 0) previewScroll?.scrollBy(delta)
|
|
471
463
|
}, 1)
|
|
@@ -480,15 +472,14 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
480
472
|
}
|
|
481
473
|
}
|
|
482
474
|
|
|
483
|
-
const loadPreviewAfter = (
|
|
475
|
+
const loadPreviewAfter = (visibleLoad = false) => {
|
|
484
476
|
const item = selectedResult()
|
|
485
477
|
const last = previewParts().at(-1)
|
|
486
|
-
if (!item ||
|
|
478
|
+
if (!item || !last || loadingPreviewMore() || prefetchingPreviewAfter()) return
|
|
487
479
|
visibleLoad ? setLoadingPreviewMore(true) : setPrefetchingPreviewAfter(true)
|
|
488
480
|
debug.time("preview:load-after")
|
|
489
481
|
try {
|
|
490
482
|
const page = loadConversationAfter(item, { id: last.id, timeCreated: last.timeCreated }, { limit: PREVIEW_PAGE_SIZE, dbPath: dbPath() })
|
|
491
|
-
if (selectedResult()?.id !== itemId) return
|
|
492
483
|
debug.log("preview:load-after", {
|
|
493
484
|
item: item.id,
|
|
494
485
|
added: page.parts.length,
|
|
@@ -508,8 +499,7 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
508
499
|
}
|
|
509
500
|
|
|
510
501
|
const schedulePreviewBefore = (previousContentHeight = previewContentHeight(), preserveScroll = true, visibleLoad = false) => {
|
|
511
|
-
|
|
512
|
-
if (!item || !hasMorePreviewBefore() || loadingPreviewMore() || prefetchingPreviewBefore()) return
|
|
502
|
+
if (!hasMorePreviewBefore() || loadingPreviewMore() || prefetchingPreviewBefore()) return
|
|
513
503
|
if (previewBeforeTimer) {
|
|
514
504
|
if (pendingPreviewBefore) {
|
|
515
505
|
pendingPreviewBefore.preserveScroll = pendingPreviewBefore.preserveScroll && preserveScroll
|
|
@@ -517,27 +507,26 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
517
507
|
}
|
|
518
508
|
return
|
|
519
509
|
}
|
|
520
|
-
pendingPreviewBefore = {
|
|
521
|
-
debug.log("preview:prefetch-before-scheduled", {
|
|
510
|
+
pendingPreviewBefore = { previousContentHeight, preserveScroll, visibleLoad }
|
|
511
|
+
debug.log("preview:prefetch-before-scheduled", { preserveScroll, visibleLoad })
|
|
522
512
|
previewBeforeTimer = setTimeout(() => {
|
|
523
513
|
const pending = pendingPreviewBefore
|
|
524
514
|
previewBeforeTimer = undefined
|
|
525
515
|
pendingPreviewBefore = undefined
|
|
526
|
-
if (pending
|
|
516
|
+
if (pending) loadPreviewBefore(pending.previousContentHeight, pending.preserveScroll, pending.visibleLoad)
|
|
527
517
|
}, 1)
|
|
528
518
|
}
|
|
529
519
|
|
|
530
520
|
const schedulePreviewAfter = (visibleLoad = false) => {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
pendingPreviewAfter = { itemId: item.id, visibleLoad: (pendingPreviewAfter?.visibleLoad || visibleLoad) }
|
|
521
|
+
if (!hasMorePreviewAfter() || loadingPreviewMore() || prefetchingPreviewAfter()) return
|
|
522
|
+
pendingPreviewAfterVisible = pendingPreviewAfterVisible || visibleLoad
|
|
534
523
|
if (previewAfterTimer) return
|
|
535
|
-
debug.log("preview:prefetch-after-scheduled", {
|
|
524
|
+
debug.log("preview:prefetch-after-scheduled", { visibleLoad })
|
|
536
525
|
previewAfterTimer = setTimeout(() => {
|
|
537
|
-
const
|
|
526
|
+
const pendingVisibleLoad = pendingPreviewAfterVisible
|
|
538
527
|
previewAfterTimer = undefined
|
|
539
|
-
|
|
540
|
-
|
|
528
|
+
pendingPreviewAfterVisible = false
|
|
529
|
+
loadPreviewAfter(pendingVisibleLoad)
|
|
541
530
|
}, 1)
|
|
542
531
|
}
|
|
543
532
|
|
|
@@ -554,62 +543,33 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
554
543
|
if (item.id === lastPreviewItemId) return
|
|
555
544
|
lastPreviewItemId = item.id
|
|
556
545
|
cancelPreviewPrefetch()
|
|
557
|
-
resetPreviewScroll()
|
|
558
|
-
solidBatch(() => {
|
|
559
|
-
setPreviewParts([])
|
|
560
|
-
setHasMorePreviewBefore(false)
|
|
561
|
-
setHasMorePreviewAfter(false)
|
|
562
|
-
setPreviewEdgeLoadingReady(false)
|
|
563
|
-
})
|
|
564
546
|
debug.log("preview:new-item", item.sessionTitle?.slice(0, 40) ?? item.id.slice(-8))
|
|
565
547
|
const db = dbPath()
|
|
566
|
-
|
|
567
|
-
|
|
548
|
+
debug.time("preview:load")
|
|
549
|
+
try {
|
|
550
|
+
const page = loadConversationAround(item, { before: INITIAL_PREVIEW_BEFORE, after: INITIAL_PREVIEW_AFTER, dbPath: db })
|
|
551
|
+
debug.log("preview:init", {
|
|
552
|
+
item: item.id,
|
|
553
|
+
session: item.sessionID,
|
|
554
|
+
parts: page.parts.length,
|
|
555
|
+
hasMoreBefore: page.hasMoreBefore,
|
|
556
|
+
hasMoreAfter: page.hasMoreAfter,
|
|
557
|
+
first: page.parts[0]?.id,
|
|
558
|
+
last: page.parts.at(-1)?.id,
|
|
559
|
+
})
|
|
560
|
+
setPreviewParts(page.parts)
|
|
561
|
+
setHasMorePreviewBefore(page.hasMoreBefore)
|
|
562
|
+
setHasMorePreviewAfter(page.hasMoreAfter)
|
|
563
|
+
} catch {}
|
|
568
564
|
debug.timeEnd("nav:total")
|
|
569
|
-
|
|
570
|
-
if (selectedResult()?.id !== itemId) {
|
|
571
|
-
debug.log("preview:load:cancelled", { item: itemId })
|
|
572
|
-
return
|
|
573
|
-
}
|
|
574
|
-
debug.time("preview:load")
|
|
575
|
-
try {
|
|
576
|
-
const page = loadConversationAround(item, { before: INITIAL_PREVIEW_BEFORE, after: INITIAL_PREVIEW_AFTER, dbPath: db })
|
|
577
|
-
if (selectedResult()?.id !== itemId) {
|
|
578
|
-
debug.log("preview:load:cancelled", { item: itemId, afterLoad: true })
|
|
579
|
-
return
|
|
580
|
-
}
|
|
581
|
-
debug.log("preview:init", {
|
|
582
|
-
item: item.id,
|
|
583
|
-
session: item.sessionID,
|
|
584
|
-
parts: page.parts.length,
|
|
585
|
-
hasMoreBefore: page.hasMoreBefore,
|
|
586
|
-
hasMoreAfter: page.hasMoreAfter,
|
|
587
|
-
first: page.parts[0]?.id,
|
|
588
|
-
last: page.parts.at(-1)?.id,
|
|
589
|
-
})
|
|
590
|
-
solidBatch(() => {
|
|
591
|
-
setPreviewParts(page.parts)
|
|
592
|
-
setHasMorePreviewBefore(page.hasMoreBefore)
|
|
593
|
-
setHasMorePreviewAfter(page.hasMoreAfter)
|
|
594
|
-
})
|
|
595
|
-
} catch {
|
|
596
|
-
} finally {
|
|
597
|
-
debug.timeEnd("preview:load")
|
|
598
|
-
}
|
|
599
|
-
}, INITIAL_PREVIEW_DELAY_MS)
|
|
600
|
-
onCleanup(() => {
|
|
601
|
-
clearTimeout(timer)
|
|
602
|
-
if (selectedResult()?.id !== itemId) {
|
|
603
|
-
debug.log("preview:load:cancelled", { item: itemId, cleanup: true })
|
|
604
|
-
}
|
|
605
|
-
})
|
|
565
|
+
debug.timeEnd("preview:load")
|
|
606
566
|
})
|
|
607
567
|
|
|
608
568
|
createEffect(() => {
|
|
609
569
|
const item = selectedResult()
|
|
610
570
|
if (!item) return
|
|
611
571
|
const interval = setInterval(() => {
|
|
612
|
-
if (
|
|
572
|
+
if (loadingPreviewMore() || prefetchingPreviewBefore() || prefetchingPreviewAfter()) return
|
|
613
573
|
const scroll = previewScroll
|
|
614
574
|
const children = scroll?.getChildren()
|
|
615
575
|
if (!scroll || !children || children.length === 0) return
|
|
@@ -650,19 +610,8 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
650
610
|
if (!item) return
|
|
651
611
|
if (item.id === scrolledItem) return
|
|
652
612
|
scrolledItem = item.id
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
const timer = setTimeout(() => {
|
|
656
|
-
if (selectedResult()?.id !== itemId) return
|
|
657
|
-
scrollPreviewToTarget(previewScroll, messageTargetID(item))
|
|
658
|
-
readyTimer = setTimeout(() => {
|
|
659
|
-
if (selectedResult()?.id === itemId) setPreviewEdgeLoadingReady(true)
|
|
660
|
-
}, 100)
|
|
661
|
-
}, 1)
|
|
662
|
-
onCleanup(() => {
|
|
663
|
-
clearTimeout(timer)
|
|
664
|
-
if (readyTimer) clearTimeout(readyTimer)
|
|
665
|
-
})
|
|
613
|
+
const timer = setTimeout(() => scrollPreviewToTarget(previewScroll, messageTargetID(item)), 1)
|
|
614
|
+
onCleanup(() => clearTimeout(timer))
|
|
666
615
|
})
|
|
667
616
|
|
|
668
617
|
const open = () => {
|
package/ui/render-target.ts
CHANGED
|
@@ -6,8 +6,6 @@ export function previewScrollAmount(scroll: ScrollBoxRenderable | undefined) {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export function messageTargetID(item: SearchResult) {
|
|
9
|
-
if (item.partType === "tool") return `tool-inline-${item.messageID}-${item.id}`
|
|
10
|
-
if (item.partType === "reasoning") return `text-${item.messageID}-${item.id}`
|
|
11
9
|
if (item.role === "assistant") return `text-${item.messageID}-${item.id}`
|
|
12
10
|
return item.messageID
|
|
13
11
|
}
|