perf(app): performance improvements

This commit is contained in:
adamelmore
2026-01-25 06:42:33 -06:00
parent ddc4e89359
commit dcc8d1a638
4 changed files with 90 additions and 18 deletions

View File

@@ -137,6 +137,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
let scrollRef!: HTMLDivElement let scrollRef!: HTMLDivElement
let slashPopoverRef!: HTMLDivElement let slashPopoverRef!: HTMLDivElement
const mirror = { input: false }
const scrollCursorIntoView = () => { const scrollCursorIntoView = () => {
const container = scrollRef const container = scrollRef
const selection = window.getSelection() const selection = window.getSelection()
@@ -651,6 +653,25 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
() => prompt.current(), () => prompt.current(),
(currentParts) => { (currentParts) => {
const inputParts = currentParts.filter((part) => part.type !== "image") as Prompt const inputParts = currentParts.filter((part) => part.type !== "image") as Prompt
if (mirror.input) {
mirror.input = false
if (isNormalizedEditor()) return
const selection = window.getSelection()
let cursorPosition: number | null = null
if (selection && selection.rangeCount > 0 && editorRef.contains(selection.anchorNode)) {
cursorPosition = getCursorPosition(editorRef)
}
renderEditor(inputParts)
if (cursorPosition !== null) {
setCursorPosition(editorRef, cursorPosition)
}
return
}
const domParts = parseFromDOM() const domParts = parseFromDOM()
if (isNormalizedEditor() && isPromptEqual(inputParts, domParts)) return if (isNormalizedEditor() && isPromptEqual(inputParts, domParts)) return
@@ -765,6 +786,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
setStore("savedPrompt", null) setStore("savedPrompt", null)
} }
if (prompt.dirty()) { if (prompt.dirty()) {
mirror.input = true
prompt.set(DEFAULT_PROMPT, 0) prompt.set(DEFAULT_PROMPT, 0)
} }
queueScroll() queueScroll()
@@ -795,6 +817,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
setStore("savedPrompt", null) setStore("savedPrompt", null)
} }
mirror.input = true
prompt.set([...rawParts, ...images], cursorPosition) prompt.set([...rawParts, ...images], cursorPosition)
queueScroll() queueScroll()
} }

View File

@@ -26,13 +26,17 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
const view = createMemo(() => layout.view(sessionKey)) const view = createMemo(() => layout.view(sessionKey))
const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : [])) const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : []))
const usd = createMemo(
() =>
new Intl.NumberFormat(language.locale(), {
style: "currency",
currency: "USD",
}),
)
const cost = createMemo(() => { const cost = createMemo(() => {
const locale = language.locale()
const total = messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0) const total = messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0)
return new Intl.NumberFormat(locale, { return usd().format(total)
style: "currency",
currency: "USD",
}).format(total)
}) })
const context = createMemo(() => { const context = createMemo(() => {

View File

@@ -26,6 +26,14 @@ export function SessionContextTab(props: SessionContextTabProps) {
const sync = useSync() const sync = useSync()
const language = useLanguage() const language = useLanguage()
const usd = createMemo(
() =>
new Intl.NumberFormat(language.locale(), {
style: "currency",
currency: "USD",
}),
)
const ctx = createMemo(() => { const ctx = createMemo(() => {
const last = findLast(props.messages(), (x) => { const last = findLast(props.messages(), (x) => {
if (x.role !== "assistant") return false if (x.role !== "assistant") return false
@@ -62,12 +70,8 @@ export function SessionContextTab(props: SessionContextTabProps) {
}) })
const cost = createMemo(() => { const cost = createMemo(() => {
const locale = language.locale()
const total = props.messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0) const total = props.messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0)
return new Intl.NumberFormat(locale, { return usd().format(total)
style: "currency",
currency: "USD",
}).format(total)
}) })
const counts = createMemo(() => { const counts = createMemo(() => {

View File

@@ -1251,19 +1251,40 @@ export default function Page() {
autoScroll.forceScrollToBottom() autoScroll.forceScrollToBottom()
} }
const closestMessage = (node: Element | null): HTMLElement | null => {
if (!node) return null
const match = node.closest?.("[data-message-id]") as HTMLElement | null
if (match) return match
const root = node.getRootNode?.()
if (root instanceof ShadowRoot) return closestMessage(root.host)
return null
}
const getActiveMessageId = (container: HTMLDivElement) => { const getActiveMessageId = (container: HTMLDivElement) => {
const rect = container.getBoundingClientRect()
if (!rect.width || !rect.height) return
const x = Math.min(window.innerWidth - 1, Math.max(0, rect.left + rect.width / 2))
const y = Math.min(window.innerHeight - 1, Math.max(0, rect.top + 100))
const hit = document.elementFromPoint(x, y)
const host = closestMessage(hit)
const id = host?.dataset.messageId
if (id) return id
// Fallback: DOM query (handles edge hit-testing cases)
const cutoff = container.scrollTop + 100 const cutoff = container.scrollTop + 100
const nodes = container.querySelectorAll<HTMLElement>("[data-message-id]") const nodes = container.querySelectorAll<HTMLElement>("[data-message-id]")
let id: string | undefined let last: string | undefined
for (const node of nodes) { for (const node of nodes) {
const next = node.dataset.messageId const next = node.dataset.messageId
if (!next) continue if (!next) continue
if (node.offsetTop > cutoff) break if (node.offsetTop > cutoff) break
id = next last = next
} }
return id return last
} }
const scheduleScrollSpy = (container: HTMLDivElement) => { const scheduleScrollSpy = (container: HTMLDivElement) => {
@@ -1900,6 +1921,8 @@ export default function Page() {
const [positions, setPositions] = createSignal<Record<string, number>>({}) const [positions, setPositions] = createSignal<Record<string, number>>({})
const [draftTop, setDraftTop] = createSignal<number | undefined>(undefined) const [draftTop, setDraftTop] = createSignal<number | undefined>(undefined)
const empty = {} as Record<string, number>
const commentLabel = (range: SelectedLineRange) => { const commentLabel = (range: SelectedLineRange) => {
const start = Math.min(range.start, range.end) const start = Math.min(range.start, range.end)
const end = Math.max(range.start, range.end) const end = Math.max(range.start, range.end)
@@ -1933,12 +1956,22 @@ export default function Page() {
return rect.top - wrapperRect.top + Math.max(0, (rect.height - 20) / 2) return rect.top - wrapperRect.top + Math.max(0, (rect.height - 20) / 2)
} }
const equal = (a: Record<string, number>, b: Record<string, number>) => {
const aKeys = Object.keys(a)
const bKeys = Object.keys(b)
if (aKeys.length !== bKeys.length) return false
for (const key of aKeys) {
if (a[key] !== b[key]) return false
}
return true
}
const updateComments = () => { const updateComments = () => {
const el = wrap const el = wrap
const root = getRoot() const root = getRoot()
if (!el || !root) { if (!el || !root) {
setPositions({}) setPositions((prev) => (Object.keys(prev).length === 0 ? prev : empty))
setDraftTop(undefined) setDraftTop((prev) => (prev === undefined ? prev : undefined))
return return
} }
@@ -1949,7 +1982,7 @@ export default function Page() {
next[comment.id] = markerTop(el, marker) next[comment.id] = markerTop(el, marker)
} }
setPositions(next) setPositions((prev) => (equal(prev, next) ? prev : next))
const range = commenting() const range = commenting()
if (!range) { if (!range) {
@@ -1963,11 +1996,18 @@ export default function Page() {
return return
} }
setDraftTop(markerTop(el, marker)) const nextTop = markerTop(el, marker)
setDraftTop((prev) => (prev === nextTop ? prev : nextTop))
} }
let commentFrame: number | undefined
const scheduleComments = () => { const scheduleComments = () => {
requestAnimationFrame(updateComments) if (commentFrame !== undefined) return
commentFrame = requestAnimationFrame(() => {
commentFrame = undefined
updateComments()
})
} }
createEffect(() => { createEffect(() => {
@@ -2225,6 +2265,7 @@ export default function Page() {
) )
onCleanup(() => { onCleanup(() => {
if (commentFrame !== undefined) cancelAnimationFrame(commentFrame)
for (const item of codeScroll) { for (const item of codeScroll) {
item.removeEventListener("scroll", handleCodeScroll) item.removeEventListener("scroll", handleCodeScroll)
} }