perf(app): performance improvements
This commit is contained in:
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user