|
@@ -1,4 +1,16 @@
|
|
|
-import { createEffect, createMemo, createSignal, For, Index, on, onCleanup, Show, mapArray, type JSX } from "solid-js"
|
|
|
|
|
|
|
+import {
|
|
|
|
|
+ createEffect,
|
|
|
|
|
+ createMemo,
|
|
|
|
|
+ createSignal,
|
|
|
|
|
+ For,
|
|
|
|
|
+ Index,
|
|
|
|
|
+ on,
|
|
|
|
|
+ onCleanup,
|
|
|
|
|
+ Show,
|
|
|
|
|
+ mapArray,
|
|
|
|
|
+ type Accessor,
|
|
|
|
|
+ type JSX,
|
|
|
|
|
+} from "solid-js"
|
|
|
import { createStore, produce } from "solid-js/store"
|
|
import { createStore, produce } from "solid-js/store"
|
|
|
import { Dynamic } from "solid-js/web"
|
|
import { Dynamic } from "solid-js/web"
|
|
|
import { useNavigate } from "@solidjs/router"
|
|
import { useNavigate } from "@solidjs/router"
|
|
@@ -245,7 +257,7 @@ function TimelineDiffView(props: { diff: SummaryDiff }) {
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
|
<div data-slot="session-turn-diff-view" data-scrollable>
|
|
<div data-slot="session-turn-diff-view" data-scrollable>
|
|
|
- <Dynamic component={fileComponent} mode="diff" fileDiff={view.fileDiff} />
|
|
|
|
|
|
|
+ <Dynamic component={fileComponent} mode="diff" virtualize={false} fileDiff={view.fileDiff} />
|
|
|
</div>
|
|
</div>
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
@@ -415,8 +427,7 @@ export function MessageTimeline(props: {
|
|
|
if (rows.length === 0) return rows
|
|
if (rows.length === 0) return rows
|
|
|
return reuseTimelineRows(previous, [...rows, new TimelineRow.BottomSpacer()])
|
|
return reuseTimelineRows(previous, [...rows, new TimelineRow.BottomSpacer()])
|
|
|
})
|
|
})
|
|
|
- const timelineRowByKey = createMemo(() => new Map(timelineRows().map((row) => [TimelineRow.key(row), row] as const)))
|
|
|
|
|
- const timelineRowKeys = createMemo(() => [...timelineRowByKey().keys()], [] as string[], { equals: sameKeys })
|
|
|
|
|
|
|
+ const timelineRowKeys = createMemo(() => timelineRows().map(TimelineRow.key), [] as string[], { equals: sameKeys })
|
|
|
const virtualCache = createMemo(() => readTimelineCache(sessionKey(), timelineRowKeys()))
|
|
const virtualCache = createMemo(() => readTimelineCache(sessionKey(), timelineRowKeys()))
|
|
|
const messageRowIndex = createMemo(() => {
|
|
const messageRowIndex = createMemo(() => {
|
|
|
const result = new Map<string, number>()
|
|
const result = new Map<string, number>()
|
|
@@ -427,6 +438,14 @@ export function MessageTimeline(props: {
|
|
|
})
|
|
})
|
|
|
return result
|
|
return result
|
|
|
})
|
|
})
|
|
|
|
|
+ const lastAssistantGroupKey = createMemo(() => {
|
|
|
|
|
+ const result = new Map<string, string>()
|
|
|
|
|
+ timelineRows().forEach((row) => {
|
|
|
|
|
+ if (row._tag !== "AssistantPart") return
|
|
|
|
|
+ result.set(row.userMessageID, row.group.key)
|
|
|
|
|
+ })
|
|
|
|
|
+ return result
|
|
|
|
|
+ })
|
|
|
const keepMounted = createMemo(() => {
|
|
const keepMounted = createMemo(() => {
|
|
|
const id = activeMessageID()
|
|
const id = activeMessageID()
|
|
|
if (!id) return
|
|
if (!id) return
|
|
@@ -540,6 +559,7 @@ export function MessageTimeline(props: {
|
|
|
const [bar, setBar] = createStore({
|
|
const [bar, setBar] = createStore({
|
|
|
ms: pace(640),
|
|
ms: pace(640),
|
|
|
})
|
|
})
|
|
|
|
|
+ const [toolOpen, setToolOpen] = createStore<Record<string, boolean | undefined>>({})
|
|
|
|
|
|
|
|
let more: HTMLButtonElement | undefined
|
|
let more: HTMLButtonElement | undefined
|
|
|
let head: HTMLDivElement | undefined
|
|
let head: HTMLDivElement | undefined
|
|
@@ -560,6 +580,11 @@ export function MessageTimeline(props: {
|
|
|
|
|
|
|
|
const isMeasuredBottom = (root: HTMLDivElement) => root.scrollHeight - root.clientHeight - root.scrollTop <= 4
|
|
const isMeasuredBottom = (root: HTMLDivElement) => root.scrollHeight - root.clientHeight - root.scrollTop <= 4
|
|
|
|
|
|
|
|
|
|
+ const measureTimeline = () => {
|
|
|
|
|
+ virtualizer?.measure()
|
|
|
|
|
+ anchorMeasuredBottom()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
function anchorMeasuredBottom() {
|
|
function anchorMeasuredBottom() {
|
|
|
if (!listRoot) return false
|
|
if (!listRoot) return false
|
|
|
if (!measuredBottomAnchored) return false
|
|
if (!measuredBottomAnchored) return false
|
|
@@ -980,29 +1005,40 @@ export function MessageTimeline(props: {
|
|
|
|
|
|
|
|
const getMsgPart = (messageID: string, partID: string) => getMsgParts(messageID).find((part) => part.id === partID)
|
|
const getMsgPart = (messageID: string, partID: string) => getMsgParts(messageID).find((part) => part.id === partID)
|
|
|
|
|
|
|
|
- const renderAssistantPartGroup = (row: TimelineRowMap["AssistantPart"]) => {
|
|
|
|
|
- if (row.group.type === "context") {
|
|
|
|
|
|
|
+ const renderAssistantPartGroup = (row: Accessor<TimelineRowMap["AssistantPart"]>) => {
|
|
|
|
|
+ if (row().group.type === "context") {
|
|
|
const parts = createMemo(() => {
|
|
const parts = createMemo(() => {
|
|
|
- const group = row.group
|
|
|
|
|
|
|
+ const group = row().group
|
|
|
if (group.type !== "context") return emptyTools
|
|
if (group.type !== "context") return emptyTools
|
|
|
return group.refs
|
|
return group.refs
|
|
|
.map((ref) => getMsgPart(ref.messageID, ref.partID))
|
|
.map((ref) => getMsgPart(ref.messageID, ref.partID))
|
|
|
.filter((part): part is ToolPart => part?.type === "tool")
|
|
.filter((part): part is ToolPart => part?.type === "tool")
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- return <ContextToolGroup parts={parts()} busy={workingTurn(row.userMessageID) && row.lastAssistantPart} />
|
|
|
|
|
|
|
+ return (
|
|
|
|
|
+ <ContextToolGroup
|
|
|
|
|
+ parts={parts()}
|
|
|
|
|
+ busy={workingTurn(row().userMessageID) && lastAssistantGroupKey().get(row().userMessageID) === row().group.key}
|
|
|
|
|
+ onSizeChange={measureTimeline}
|
|
|
|
|
+ />
|
|
|
|
|
+ )
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const message = createMemo(() => {
|
|
const message = createMemo(() => {
|
|
|
- const group = row.group
|
|
|
|
|
|
|
+ const group = row().group
|
|
|
if (group.type !== "part") return
|
|
if (group.type !== "part") return
|
|
|
return messageByID().get(group.ref.messageID)
|
|
return messageByID().get(group.ref.messageID)
|
|
|
})
|
|
})
|
|
|
const part = createMemo(() => {
|
|
const part = createMemo(() => {
|
|
|
- const group = row.group
|
|
|
|
|
|
|
+ const group = row().group
|
|
|
if (group.type !== "part") return
|
|
if (group.type !== "part") return
|
|
|
return getMsgPart(group.ref.messageID, group.ref.partID)
|
|
return getMsgPart(group.ref.messageID, group.ref.partID)
|
|
|
})
|
|
})
|
|
|
|
|
+ const defaultOpen = createMemo(() => {
|
|
|
|
|
+ const item = part()
|
|
|
|
|
+ if (!item) return
|
|
|
|
|
+ return partDefaultOpen(item, settings.general.shellToolPartsExpanded(), settings.general.editToolPartsExpanded())
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
|
<Show when={message()}>
|
|
<Show when={message()}>
|
|
@@ -1012,14 +1048,13 @@ export function MessageTimeline(props: {
|
|
|
<MessagePart
|
|
<MessagePart
|
|
|
part={part()}
|
|
part={part()}
|
|
|
message={message()}
|
|
message={message()}
|
|
|
- showAssistantCopyPartID={assistantCopyPartID(row.userMessageID)}
|
|
|
|
|
- turnDurationMs={turnDurationMs(row.userMessageID)}
|
|
|
|
|
- defaultOpen={partDefaultOpen(
|
|
|
|
|
- part(),
|
|
|
|
|
- settings.general.shellToolPartsExpanded(),
|
|
|
|
|
- settings.general.editToolPartsExpanded(),
|
|
|
|
|
- )}
|
|
|
|
|
|
|
+ showAssistantCopyPartID={assistantCopyPartID(row().userMessageID)}
|
|
|
|
|
+ turnDurationMs={turnDurationMs(row().userMessageID)}
|
|
|
|
|
+ defaultOpen={defaultOpen()}
|
|
|
|
|
+ toolOpen={toolOpen[part().id] ?? defaultOpen()}
|
|
|
|
|
+ onToolOpenChange={(open) => setToolOpen(part().id, open)}
|
|
|
deferToolContent={false}
|
|
deferToolContent={false}
|
|
|
|
|
+ virtualizeDiff={false}
|
|
|
/>
|
|
/>
|
|
|
)}
|
|
)}
|
|
|
</Show>
|
|
</Show>
|
|
@@ -1028,25 +1063,25 @@ export function MessageTimeline(props: {
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- function TimelineRowFrame(input: { row: FramedTimelineRow; children: JSX.Element }) {
|
|
|
|
|
|
|
+ function TimelineRowFrame(input: { row: Accessor<FramedTimelineRow>; children: JSX.Element }) {
|
|
|
const anchor = () => {
|
|
const anchor = () => {
|
|
|
- const row = input.row
|
|
|
|
|
|
|
+ const row = input.row()
|
|
|
return row._tag === "CommentStrip" || (row._tag === "UserMessage" && row.anchor)
|
|
return row._tag === "CommentStrip" || (row._tag === "UserMessage" && row.anchor)
|
|
|
}
|
|
}
|
|
|
const previousUserMessage = () => {
|
|
const previousUserMessage = () => {
|
|
|
- const row = input.row
|
|
|
|
|
|
|
+ const row = input.row()
|
|
|
return (row._tag === "CommentStrip" || row._tag === "UserMessage") && row.previousUserMessage
|
|
return (row._tag === "CommentStrip" || row._tag === "UserMessage") && row.previousUserMessage
|
|
|
}
|
|
}
|
|
|
const previousAssistantPart = () => {
|
|
const previousAssistantPart = () => {
|
|
|
- const row = input.row
|
|
|
|
|
|
|
+ const row = input.row()
|
|
|
return row._tag === "AssistantPart" && row.previousAssistantPart
|
|
return row._tag === "AssistantPart" && row.previousAssistantPart
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
|
<div
|
|
<div
|
|
|
- id={anchor() ? props.anchor(input.row.userMessageID) : undefined}
|
|
|
|
|
- data-message-id={input.row.userMessageID}
|
|
|
|
|
- data-timeline-row={input.row._tag}
|
|
|
|
|
|
|
+ id={anchor() ? props.anchor(input.row().userMessageID) : undefined}
|
|
|
|
|
+ data-message-id={input.row().userMessageID}
|
|
|
|
|
+ data-timeline-row={input.row()._tag}
|
|
|
classList={{
|
|
classList={{
|
|
|
"min-w-0 w-full max-w-full": true,
|
|
"min-w-0 w-full max-w-full": true,
|
|
|
"md:max-w-200 2xl:max-w-[1000px]": props.centered,
|
|
"md:max-w-200 2xl:max-w-[1000px]": props.centered,
|
|
@@ -1062,14 +1097,15 @@ export function MessageTimeline(props: {
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const renderTimelineRow = (row: TimelineRow.TimelineRow) => {
|
|
|
|
|
- switch (row._tag) {
|
|
|
|
|
|
|
+ const renderTimelineRow = (row: Accessor<TimelineRow.TimelineRow>) => {
|
|
|
|
|
+ switch (row()._tag) {
|
|
|
case "CommentStrip": {
|
|
case "CommentStrip": {
|
|
|
|
|
+ const commentStripRow = row as Accessor<TimelineRowByTag<"CommentStrip">>
|
|
|
const comments = createMemo(() =>
|
|
const comments = createMemo(() =>
|
|
|
- getMsgParts(row.userMessageID).flatMap((part) => MessageComment.fromPart(part) ?? []),
|
|
|
|
|
|
|
+ getMsgParts(commentStripRow().userMessageID).flatMap((part) => MessageComment.fromPart(part) ?? []),
|
|
|
)
|
|
)
|
|
|
return (
|
|
return (
|
|
|
- <TimelineRowFrame row={row}>
|
|
|
|
|
|
|
+ <TimelineRowFrame row={commentStripRow}>
|
|
|
<div class="w-full px-4 md:px-5 pb-2">
|
|
<div class="w-full px-4 md:px-5 pb-2">
|
|
|
<div class="ml-auto max-w-[82%] overflow-x-auto no-scrollbar">
|
|
<div class="ml-auto max-w-[82%] overflow-x-auto no-scrollbar">
|
|
|
<div class="flex w-max min-w-full justify-end gap-2">
|
|
<div class="flex w-max min-w-full justify-end gap-2">
|
|
@@ -1102,17 +1138,18 @@ export function MessageTimeline(props: {
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
case "UserMessage": {
|
|
case "UserMessage": {
|
|
|
|
|
+ const userMessageRow = row as Accessor<TimelineRowByTag<"UserMessage">>
|
|
|
const message = createMemo(() => {
|
|
const message = createMemo(() => {
|
|
|
- const m = messageByID().get(row.userMessageID)
|
|
|
|
|
|
|
+ const m = messageByID().get(userMessageRow().userMessageID)
|
|
|
if (m?.role === "user") return m
|
|
if (m?.role === "user") return m
|
|
|
})
|
|
})
|
|
|
return (
|
|
return (
|
|
|
- <TimelineRowFrame row={row}>
|
|
|
|
|
|
|
+ <TimelineRowFrame row={userMessageRow}>
|
|
|
<Show when={message()}>
|
|
<Show when={message()}>
|
|
|
{(message) => (
|
|
{(message) => (
|
|
|
<div data-slot="session-turn-message-container" class="w-full px-4 md:px-5">
|
|
<div data-slot="session-turn-message-container" class="w-full px-4 md:px-5">
|
|
|
<div data-slot="session-turn-message-content" aria-live="off">
|
|
<div data-slot="session-turn-message-content" aria-live="off">
|
|
|
- <Message message={message()} parts={getMsgParts(row.userMessageID)} actions={props.actions} />
|
|
|
|
|
|
|
+ <Message message={message()} parts={getMsgParts(userMessageRow().userMessageID)} actions={props.actions} />
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
)}
|
|
)}
|
|
@@ -1121,13 +1158,14 @@ export function MessageTimeline(props: {
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
case "TurnDivider": {
|
|
case "TurnDivider": {
|
|
|
|
|
+ const turnDividerRow = row as Accessor<TimelineRowByTag<"TurnDivider">>
|
|
|
return (
|
|
return (
|
|
|
- <TimelineRowFrame row={row}>
|
|
|
|
|
|
|
+ <TimelineRowFrame row={turnDividerRow}>
|
|
|
<div data-slot="session-turn-message-container" class="w-full px-4 md:px-5">
|
|
<div data-slot="session-turn-message-container" class="w-full px-4 md:px-5">
|
|
|
<div data-slot="session-turn-compaction">
|
|
<div data-slot="session-turn-compaction">
|
|
|
<MessageDivider
|
|
<MessageDivider
|
|
|
label={language.t(
|
|
label={language.t(
|
|
|
- row.label === "compaction" ? "ui.messagePart.compaction" : "ui.message.interrupted",
|
|
|
|
|
|
|
+ turnDividerRow().label === "compaction" ? "ui.messagePart.compaction" : "ui.message.interrupted",
|
|
|
)}
|
|
)}
|
|
|
/>
|
|
/>
|
|
|
</div>
|
|
</div>
|
|
@@ -1136,22 +1174,24 @@ export function MessageTimeline(props: {
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
case "AssistantPart": {
|
|
case "AssistantPart": {
|
|
|
|
|
+ const assistantPartRow = row as Accessor<TimelineRowByTag<"AssistantPart">>
|
|
|
return (
|
|
return (
|
|
|
- <TimelineRowFrame row={row}>
|
|
|
|
|
|
|
+ <TimelineRowFrame row={assistantPartRow}>
|
|
|
<div data-slot="session-turn-message-container" class="w-full px-4 md:px-5">
|
|
<div data-slot="session-turn-message-container" class="w-full px-4 md:px-5">
|
|
|
- <div data-slot="session-turn-assistant-content" aria-hidden={workingTurn(row.userMessageID)}>
|
|
|
|
|
- {renderAssistantPartGroup(row)}
|
|
|
|
|
|
|
+ <div data-slot="session-turn-assistant-content" aria-hidden={workingTurn(assistantPartRow().userMessageID)}>
|
|
|
|
|
+ {renderAssistantPartGroup(assistantPartRow)}
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</TimelineRowFrame>
|
|
</TimelineRowFrame>
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
case "Thinking": {
|
|
case "Thinking": {
|
|
|
|
|
+ const thinkingRow = row as Accessor<TimelineRowByTag<"Thinking">>
|
|
|
return (
|
|
return (
|
|
|
- <TimelineRowFrame row={row}>
|
|
|
|
|
|
|
+ <TimelineRowFrame row={thinkingRow}>
|
|
|
<div data-slot="session-turn-message-container" class="w-full px-4 md:px-5">
|
|
<div data-slot="session-turn-message-container" class="w-full px-4 md:px-5">
|
|
|
<TimelineThinkingRow
|
|
<TimelineThinkingRow
|
|
|
- reasoningHeading={row.reasoningHeading}
|
|
|
|
|
|
|
+ reasoningHeading={thinkingRow().reasoningHeading}
|
|
|
showReasoningSummaries={settings.general.showReasoningSummaries()}
|
|
showReasoningSummaries={settings.general.showReasoningSummaries()}
|
|
|
/>
|
|
/>
|
|
|
</div>
|
|
</div>
|
|
@@ -1159,29 +1199,32 @@ export function MessageTimeline(props: {
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
case "Retry": {
|
|
case "Retry": {
|
|
|
|
|
+ const retryRow = row as Accessor<TimelineRowByTag<"Retry">>
|
|
|
return (
|
|
return (
|
|
|
- <TimelineRowFrame row={row}>
|
|
|
|
|
|
|
+ <TimelineRowFrame row={retryRow}>
|
|
|
<div data-slot="session-turn-message-container" class="w-full px-4 md:px-5">
|
|
<div data-slot="session-turn-message-container" class="w-full px-4 md:px-5">
|
|
|
- <SessionRetry status={sessionStatus()} show={activeMessageID() === row.userMessageID} />
|
|
|
|
|
|
|
+ <SessionRetry status={sessionStatus()} show={activeMessageID() === retryRow().userMessageID} />
|
|
|
</div>
|
|
</div>
|
|
|
</TimelineRowFrame>
|
|
</TimelineRowFrame>
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
case "DiffSummary": {
|
|
case "DiffSummary": {
|
|
|
|
|
+ const diffSummaryRow = row as Accessor<TimelineRowByTag<"DiffSummary">>
|
|
|
return (
|
|
return (
|
|
|
- <TimelineRowFrame row={row}>
|
|
|
|
|
|
|
+ <TimelineRowFrame row={diffSummaryRow}>
|
|
|
<div data-slot="session-turn-message-container" class="w-full px-4 md:px-5">
|
|
<div data-slot="session-turn-message-container" class="w-full px-4 md:px-5">
|
|
|
- <TimelineDiffSummaryRow diffs={row.diffs} />
|
|
|
|
|
|
|
+ <TimelineDiffSummaryRow diffs={diffSummaryRow().diffs} />
|
|
|
</div>
|
|
</div>
|
|
|
</TimelineRowFrame>
|
|
</TimelineRowFrame>
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
case "Error": {
|
|
case "Error": {
|
|
|
|
|
+ const errorRow = row as Accessor<TimelineRowByTag<"Error">>
|
|
|
return (
|
|
return (
|
|
|
- <TimelineRowFrame row={row}>
|
|
|
|
|
|
|
+ <TimelineRowFrame row={errorRow}>
|
|
|
<div data-slot="session-turn-message-container" class="w-full px-4 md:px-5">
|
|
<div data-slot="session-turn-message-container" class="w-full px-4 md:px-5">
|
|
|
<Card variant="error" class="error-card">
|
|
<Card variant="error" class="error-card">
|
|
|
- {row.text}
|
|
|
|
|
|
|
+ {errorRow().text}
|
|
|
</Card>
|
|
</Card>
|
|
|
</div>
|
|
</div>
|
|
|
</TimelineRowFrame>
|
|
</TimelineRowFrame>
|
|
@@ -1192,12 +1235,8 @@ export function MessageTimeline(props: {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- function TimelineRowView(props: { rowKey: string }) {
|
|
|
|
|
- return (
|
|
|
|
|
- <Show when={timelineRowByKey().get(props.rowKey)} keyed>
|
|
|
|
|
- {(item) => renderTimelineRow(item)}
|
|
|
|
|
- </Show>
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ function TimelineRowView(props: { row: TimelineRow.TimelineRow }) {
|
|
|
|
|
+ return renderTimelineRow(() => props.row)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
@@ -1529,7 +1568,7 @@ export function MessageTimeline(props: {
|
|
|
<Show when={scrollRoot()}>
|
|
<Show when={scrollRoot()}>
|
|
|
{(root) => (
|
|
{(root) => (
|
|
|
<Virtualizer
|
|
<Virtualizer
|
|
|
- data={timelineRowKeys()}
|
|
|
|
|
|
|
+ data={timelineRows()}
|
|
|
cache={virtualCache()}
|
|
cache={virtualCache()}
|
|
|
itemSize={virtualCache() ? undefined : timelineFallbackItemSize}
|
|
itemSize={virtualCache() ? undefined : timelineFallbackItemSize}
|
|
|
scrollRef={root()}
|
|
scrollRef={root()}
|
|
@@ -1549,7 +1588,7 @@ export function MessageTimeline(props: {
|
|
|
scheduleContentRoot(root())
|
|
scheduleContentRoot(root())
|
|
|
}}
|
|
}}
|
|
|
>
|
|
>
|
|
|
- {(key) => <TimelineRowView rowKey={key} />}
|
|
|
|
|
|
|
+ {(row) => <TimelineRowView row={row} />}
|
|
|
</Virtualizer>
|
|
</Virtualizer>
|
|
|
)}
|
|
)}
|
|
|
</Show>
|
|
</Show>
|