feat(app): improved session layout
This commit is contained in:
@@ -28,13 +28,14 @@ import { IconButton } from "@opencode-ai/ui/icon-button"
|
|||||||
import { InlineInput } from "@opencode-ai/ui/inline-input"
|
import { InlineInput } from "@opencode-ai/ui/inline-input"
|
||||||
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
||||||
import { HoverCard } from "@opencode-ai/ui/hover-card"
|
import { HoverCard } from "@opencode-ai/ui/hover-card"
|
||||||
|
import { MessageNav } from "@opencode-ai/ui/message-nav"
|
||||||
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
||||||
import { Collapsible } from "@opencode-ai/ui/collapsible"
|
import { Collapsible } from "@opencode-ai/ui/collapsible"
|
||||||
import { DiffChanges } from "@opencode-ai/ui/diff-changes"
|
import { DiffChanges } from "@opencode-ai/ui/diff-changes"
|
||||||
import { Spinner } from "@opencode-ai/ui/spinner"
|
import { Spinner } from "@opencode-ai/ui/spinner"
|
||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||||
import { getFilename } from "@opencode-ai/util/path"
|
import { getFilename } from "@opencode-ai/util/path"
|
||||||
import { Session } from "@opencode-ai/sdk/v2/client"
|
import { Session, type Message, type TextPart } from "@opencode-ai/sdk/v2/client"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
import { createStore, produce, reconcile } from "solid-js/store"
|
import { createStore, produce, reconcile } from "solid-js/store"
|
||||||
import {
|
import {
|
||||||
@@ -1329,63 +1330,104 @@ export default function Layout(props: ParentProps) {
|
|||||||
return agent?.color
|
return agent?.color
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const hoverMessages = createMemo(() =>
|
||||||
|
sessionStore.message[props.session.id]?.filter((message) => message.role === "user"),
|
||||||
|
)
|
||||||
|
const hoverReady = createMemo(() => sessionStore.message[props.session.id] !== undefined)
|
||||||
|
const hoverAllowed = createMemo(() => !props.mobile && layout.sidebar.opened())
|
||||||
|
const isActive = createMemo(() => props.session.id === params.id)
|
||||||
|
|
||||||
|
const messageLabel = (message: Message) => {
|
||||||
|
const parts = sessionStore.part[message.id] ?? []
|
||||||
|
const text = parts.find((part): part is TextPart => part?.type === "text" && !part.synthetic && !part.ignored)
|
||||||
|
return text?.text
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = (
|
||||||
|
<A
|
||||||
|
href={`${props.slug}/session/${props.session.id}`}
|
||||||
|
class={`flex items-center justify-between gap-3 min-w-0 text-left w-full focus:outline-none transition-[padding] group-hover/session:pr-7 group-focus-within/session:pr-7 group-active/session:pr-7 ${props.dense ? "py-0.5" : "py-1"}`}
|
||||||
|
onMouseEnter={() => prefetchSession(props.session, "high")}
|
||||||
|
onFocus={() => prefetchSession(props.session, "high")}
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-1 w-full">
|
||||||
|
<div
|
||||||
|
class="shrink-0 size-6 flex items-center justify-center"
|
||||||
|
style={{ color: tint() ?? "var(--icon-interactive-base)" }}
|
||||||
|
>
|
||||||
|
<Switch fallback={<Icon name="dash" size="small" class="text-icon-weak" />}>
|
||||||
|
<Match when={isWorking()}>
|
||||||
|
<Spinner class="size-[15px]" />
|
||||||
|
</Match>
|
||||||
|
<Match when={hasPermissions()}>
|
||||||
|
<div class="size-1.5 rounded-full bg-surface-warning-strong" />
|
||||||
|
</Match>
|
||||||
|
<Match when={hasError()}>
|
||||||
|
<div class="size-1.5 rounded-full bg-text-diff-delete-base" />
|
||||||
|
</Match>
|
||||||
|
<Match when={notifications().length > 0}>
|
||||||
|
<div class="size-1.5 rounded-full bg-text-interactive-base" />
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
<Tooltip
|
||||||
|
inactive={hoverAllowed()}
|
||||||
|
placement="top-start"
|
||||||
|
value={props.session.title}
|
||||||
|
gutter={0}
|
||||||
|
openDelay={3000}
|
||||||
|
class="grow-1 min-w-0"
|
||||||
|
>
|
||||||
|
<InlineEditor
|
||||||
|
id={`session:${props.session.id}`}
|
||||||
|
value={() => props.session.title}
|
||||||
|
onSave={(next) => renameSession(props.session, next)}
|
||||||
|
class="text-14-regular text-text-strong grow-1 min-w-0 overflow-hidden text-ellipsis truncate"
|
||||||
|
displayClass="text-14-regular text-text-strong grow-1 min-w-0 overflow-hidden text-ellipsis truncate"
|
||||||
|
stopPropagation
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Show when={props.session.summary}>
|
||||||
|
{(summary) => (
|
||||||
|
<div class="group-hover/session:hidden group-active/session:hidden group-focus-within/session:hidden">
|
||||||
|
<DiffChanges changes={summary()} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</A>
|
||||||
|
))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-session-id={props.session.id}
|
data-session-id={props.session.id}
|
||||||
class="group/session relative w-full rounded-md cursor-default transition-colors pl-2 pr-3
|
class="group/session relative w-full rounded-md cursor-default transition-colors pl-2 pr-3
|
||||||
hover:bg-surface-raised-base-hover focus-within:bg-surface-raised-base-hover has-[.active]:bg-surface-base-active"
|
hover:bg-surface-raised-base-hover focus-within:bg-surface-raised-base-hover has-[.active]:bg-surface-base-active"
|
||||||
>
|
>
|
||||||
<A
|
<Show
|
||||||
href={`${props.slug}/session/${props.session.id}`}
|
when={hoverAllowed()}
|
||||||
class={`flex items-center justify-between gap-3 min-w-0 text-left w-full focus:outline-none transition-[padding] group-hover/session:pr-7 group-focus-within/session:pr-7 group-active/session:pr-7 ${props.dense ? "py-0.5" : "py-1"}`}
|
fallback={item}
|
||||||
onMouseEnter={() => prefetchSession(props.session, "high")}
|
|
||||||
onFocus={() => prefetchSession(props.session, "high")}
|
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-1 w-full">
|
<HoverCard openDelay={150} closeDelay={100} placement="right" gutter={12} trigger={item}>
|
||||||
<div
|
<Show when={hoverReady()} fallback={<div class="text-12-regular text-text-weak">Loading messages…</div>}>
|
||||||
class="shrink-0 size-6 flex items-center justify-center"
|
<MessageNav
|
||||||
style={{ color: tint() ?? "var(--icon-interactive-base)" }}
|
messages={hoverMessages() ?? []}
|
||||||
>
|
current={undefined}
|
||||||
<Switch fallback={<Icon name="dash" size="small" class="text-icon-weak" />}>
|
getLabel={messageLabel}
|
||||||
<Match when={isWorking()}>
|
onMessageSelect={(message) => {
|
||||||
<Spinner class="size-[15px]" />
|
if (!isActive()) {
|
||||||
</Match>
|
navigate(`${props.slug}/session/${props.session.id}#message-${message.id}`)
|
||||||
<Match when={hasPermissions()}>
|
return
|
||||||
<div class="size-1.5 rounded-full bg-surface-warning-strong" />
|
}
|
||||||
</Match>
|
window.location.hash = `message-${message.id}`
|
||||||
<Match when={hasError()}>
|
window.dispatchEvent(new HashChangeEvent("hashchange"))
|
||||||
<div class="size-1.5 rounded-full bg-text-diff-delete-base" />
|
}}
|
||||||
</Match>
|
size="normal"
|
||||||
<Match when={notifications().length > 0}>
|
class="w-60"
|
||||||
<div class="size-1.5 rounded-full bg-text-interactive-base" />
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
|
||||||
</div>
|
|
||||||
<Tooltip
|
|
||||||
placement="top-start"
|
|
||||||
value={props.session.title}
|
|
||||||
gutter={0}
|
|
||||||
openDelay={3000}
|
|
||||||
class="grow-1 min-w-0"
|
|
||||||
>
|
|
||||||
<InlineEditor
|
|
||||||
id={`session:${props.session.id}`}
|
|
||||||
value={() => props.session.title}
|
|
||||||
onSave={(next) => renameSession(props.session, next)}
|
|
||||||
class="text-14-regular text-text-strong grow-1 min-w-0 overflow-hidden text-ellipsis truncate"
|
|
||||||
displayClass="text-14-regular text-text-strong grow-1 min-w-0 overflow-hidden text-ellipsis truncate"
|
|
||||||
stopPropagation
|
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
|
||||||
<Show when={props.session.summary}>
|
|
||||||
{(summary) => (
|
|
||||||
<div class="group-hover/session:hidden group-active/session:hidden group-focus-within/session:hidden">
|
|
||||||
<DiffChanges changes={summary()} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</HoverCard>
|
||||||
</A>
|
</Show>
|
||||||
<div
|
<div
|
||||||
class={`hidden group-hover/session:flex group-active/session:flex group-focus-within/session:flex text-text-base gap-1 items-center absolute ${props.dense ? "top-0.5 right-0.5" : "top-1 right-1"}`}
|
class={`hidden group-hover/session:flex group-active/session:flex group-focus-within/session:flex text-text-base gap-1 items-center absolute ${props.dense ? "top-0.5 right-0.5" : "top-1 right-1"}`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import { useCodeComponent } from "@opencode-ai/ui/context/code"
|
|||||||
import { SessionTurn } from "@opencode-ai/ui/session-turn"
|
import { SessionTurn } from "@opencode-ai/ui/session-turn"
|
||||||
import { createAutoScroll } from "@opencode-ai/ui/hooks"
|
import { createAutoScroll } from "@opencode-ai/ui/hooks"
|
||||||
import { SessionReview } from "@opencode-ai/ui/session-review"
|
import { SessionReview } from "@opencode-ai/ui/session-review"
|
||||||
import { SessionMessageRail } from "@opencode-ai/ui/session-message-rail"
|
|
||||||
|
|
||||||
import { DragDropProvider, DragDropSensors, DragOverlay, SortableProvider, closestCenter } from "@thisbeyond/solid-dnd"
|
import { DragDropProvider, DragDropSensors, DragOverlay, SortableProvider, closestCenter } from "@thisbeyond/solid-dnd"
|
||||||
import type { DragEvent } from "@thisbeyond/solid-dnd"
|
import type { DragEvent } from "@thisbeyond/solid-dnd"
|
||||||
@@ -1163,17 +1162,6 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div class="relative w-full h-full min-w-0">
|
<div class="relative w-full h-full min-w-0">
|
||||||
<Show when={isDesktop()}>
|
|
||||||
<div class="absolute inset-0 pointer-events-none z-10">
|
|
||||||
<SessionMessageRail
|
|
||||||
messages={visibleUserMessages()}
|
|
||||||
current={activeMessage()}
|
|
||||||
onMessageSelect={scrollToMessage}
|
|
||||||
wide={!showTabs()}
|
|
||||||
class="pointer-events-auto"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
<div
|
<div
|
||||||
ref={setScrollRef}
|
ref={setScrollRef}
|
||||||
onScroll={(e) => {
|
onScroll={(e) => {
|
||||||
@@ -1255,13 +1243,7 @@ export default function Page() {
|
|||||||
root: "min-w-0 w-full relative",
|
root: "min-w-0 w-full relative",
|
||||||
content:
|
content:
|
||||||
"flex flex-col justify-between !overflow-visible [&_[data-slot=session-turn-message-header]]:top-[-32px]",
|
"flex flex-col justify-between !overflow-visible [&_[data-slot=session-turn-message-header]]:top-[-32px]",
|
||||||
container:
|
container: "w-full px-4 md:px-6",
|
||||||
"px-4 md:px-6 " +
|
|
||||||
(!showTabs()
|
|
||||||
? "md:max-w-200 md:mx-auto"
|
|
||||||
: visibleUserMessages().length > 1
|
|
||||||
? "md:pr-6 md:pl-18"
|
|
||||||
: ""),
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import { iife } from "@opencode-ai/util/iife"
|
|||||||
import { Binary } from "@opencode-ai/util/binary"
|
import { Binary } from "@opencode-ai/util/binary"
|
||||||
import { NamedError } from "@opencode-ai/util/error"
|
import { NamedError } from "@opencode-ai/util/error"
|
||||||
import { DateTime } from "luxon"
|
import { DateTime } from "luxon"
|
||||||
import { SessionMessageRail } from "@opencode-ai/ui/session-message-rail"
|
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import NotFound from "../[...404]"
|
import NotFound from "../[...404]"
|
||||||
@@ -353,26 +352,16 @@ export default function () {
|
|||||||
<div
|
<div
|
||||||
classList={{
|
classList={{
|
||||||
"@container relative shrink-0 pt-14 flex flex-col gap-10 min-h-0 w-full": true,
|
"@container relative shrink-0 pt-14 flex flex-col gap-10 min-h-0 w-full": true,
|
||||||
"mx-auto max-w-200": !wide(),
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
classList={{
|
classList={{
|
||||||
"w-full flex justify-start items-start min-w-0": true,
|
"w-full flex justify-start items-start min-w-0 px-6": true,
|
||||||
"max-w-200 mx-auto px-6": wide(),
|
|
||||||
"pr-6 pl-18": !wide() && messages().length > 1,
|
|
||||||
"px-6": !wide() && messages().length === 1,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{title()}
|
{title()}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-start justify-start h-full min-h-0">
|
<div class="flex items-start justify-start h-full min-h-0">
|
||||||
<SessionMessageRail
|
|
||||||
messages={messages()}
|
|
||||||
current={activeMessage()}
|
|
||||||
onMessageSelect={setActiveMessage}
|
|
||||||
wide={wide()}
|
|
||||||
/>
|
|
||||||
<SessionTurn
|
<SessionTurn
|
||||||
sessionID={data().sessionID}
|
sessionID={data().sessionID}
|
||||||
messageID={store.messageId ?? firstUserMessage()!.id!}
|
messageID={store.messageId ?? firstUserMessage()!.id!}
|
||||||
@@ -386,13 +375,7 @@ export default function () {
|
|||||||
classes={{
|
classes={{
|
||||||
root: "grow",
|
root: "grow",
|
||||||
content: "flex flex-col justify-between",
|
content: "flex flex-col justify-between",
|
||||||
container:
|
container: "w-full pb-20 px-6",
|
||||||
"w-full pb-20 " +
|
|
||||||
(wide()
|
|
||||||
? "max-w-200 mx-auto px-6"
|
|
||||||
: messages().length > 1
|
|
||||||
? "pr-6 pl-18"
|
|
||||||
: "px-6"),
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
[data-slot="hover-card-trigger"] {
|
[data-slot="hover-card-trigger"] {
|
||||||
display: inline-flex;
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-component="hover-card-content"] {
|
[data-component="hover-card-content"] {
|
||||||
@@ -8,6 +10,7 @@
|
|||||||
max-width: 320px;
|
max-width: 320px;
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
background-color: var(--surface-raised-stronger-non-alpha);
|
background-color: var(--surface-raised-stronger-non-alpha);
|
||||||
|
pointer-events: auto;
|
||||||
|
|
||||||
border: 1px solid color-mix(in oklch, var(--border-base) 50%, transparent);
|
border: 1px solid color-mix(in oklch, var(--border-base) 50%, transparent);
|
||||||
background-clip: padding-box;
|
background-clip: padding-box;
|
||||||
|
|||||||
@@ -10,6 +10,10 @@
|
|||||||
width: 240px;
|
width: 240px;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&[data-size="compact"] {
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="message-nav-item"] {
|
[data-slot="message-nav-item"] {
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ export function MessageNav(
|
|||||||
current?: UserMessage
|
current?: UserMessage
|
||||||
size: "normal" | "compact"
|
size: "normal" | "compact"
|
||||||
onMessageSelect: (message: UserMessage) => void
|
onMessageSelect: (message: UserMessage) => void
|
||||||
|
getLabel?: (message: UserMessage) => string | undefined
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const [local, others] = splitProps(props, ["messages", "current", "size", "onMessageSelect"])
|
const [local, others] = splitProps(props, ["messages", "current", "size", "onMessageSelect", "getLabel"])
|
||||||
|
|
||||||
const content = () => (
|
const content = () => (
|
||||||
<ul role="list" data-component="message-nav" data-size={local.size} {...others}>
|
<ul role="list" data-component="message-nav" data-size={local.size} {...others}>
|
||||||
@@ -19,23 +20,36 @@ export function MessageNav(
|
|||||||
{(message) => {
|
{(message) => {
|
||||||
const handleClick = () => local.onMessageSelect(message)
|
const handleClick = () => local.onMessageSelect(message)
|
||||||
|
|
||||||
|
const handleKeyPress = (event: KeyboardEvent) => {
|
||||||
|
if (event.key !== "Enter" && event.key !== " ") return
|
||||||
|
event.preventDefault()
|
||||||
|
local.onMessageSelect(message)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li data-slot="message-nav-item">
|
<li data-slot="message-nav-item">
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={local.size === "compact"}>
|
<Match when={local.size === "compact"}>
|
||||||
<div data-slot="message-nav-tick-button" data-active={message.id === local.current?.id || undefined}>
|
<div
|
||||||
|
data-slot="message-nav-tick-button"
|
||||||
|
data-active={message.id === local.current?.id || undefined}
|
||||||
|
role="button"
|
||||||
|
tabindex={0}
|
||||||
|
onClick={handleClick}
|
||||||
|
onKeyDown={handleKeyPress}
|
||||||
|
>
|
||||||
<div data-slot="message-nav-tick-line" />
|
<div data-slot="message-nav-tick-line" />
|
||||||
</div>
|
</div>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={local.size === "normal"}>
|
<Match when={local.size === "normal"}>
|
||||||
<button data-slot="message-nav-message-button" onClick={handleClick}>
|
<button data-slot="message-nav-message-button" onClick={handleClick} onKeyDown={handleKeyPress}>
|
||||||
<DiffChanges changes={message.summary?.diffs ?? []} variant="bars" />
|
<DiffChanges changes={message.summary?.diffs ?? []} variant="bars" />
|
||||||
<div
|
<div
|
||||||
data-slot="message-nav-title-preview"
|
data-slot="message-nav-title-preview"
|
||||||
data-active={message.id === local.current?.id || undefined}
|
data-active={message.id === local.current?.id || undefined}
|
||||||
>
|
>
|
||||||
<Show when={message.summary?.title} fallback="New message">
|
<Show when={local.getLabel?.(message) ?? message.summary?.title} fallback="New message">
|
||||||
{message.summary?.title}
|
{local.getLabel?.(message) ?? message.summary?.title}
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
[data-component="session-message-rail"] {
|
|
||||||
display: contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-message-rail-compact"],
|
|
||||||
[data-slot="session-message-rail-full"] {
|
|
||||||
position: absolute;
|
|
||||||
left: 1.5rem;
|
|
||||||
margin-top: 0.625rem;
|
|
||||||
top: 0;
|
|
||||||
bottom: 8rem;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-message-rail-compact"] {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="session-message-rail-full"] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@container (min-width: 88rem) {
|
|
||||||
[data-slot="session-message-rail-compact"] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
[data-slot="session-message-rail-full"] {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="session-message-rail"] [data-slot="session-message-rail-full"] {
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="session-message-rail"][data-wide] [data-slot="session-message-rail-full"] {
|
|
||||||
margin-top: 0.125rem;
|
|
||||||
left: calc(((100% - min(100%, 50rem)) / 2) - 1.5rem);
|
|
||||||
transform: translateX(-100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="session-message-rail"]:not([data-wide]) [data-slot="session-message-rail-full"] {
|
|
||||||
margin-top: 0.625rem;
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import { UserMessage } from "@opencode-ai/sdk/v2"
|
|
||||||
import { ComponentProps, Show, splitProps } from "solid-js"
|
|
||||||
import { MessageNav } from "./message-nav"
|
|
||||||
import "./session-message-rail.css"
|
|
||||||
|
|
||||||
export interface SessionMessageRailProps extends ComponentProps<"div"> {
|
|
||||||
messages: UserMessage[]
|
|
||||||
current?: UserMessage
|
|
||||||
wide?: boolean
|
|
||||||
onMessageSelect: (message: UserMessage) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SessionMessageRail(props: SessionMessageRailProps) {
|
|
||||||
const [local, others] = splitProps(props, ["messages", "current", "wide", "onMessageSelect", "class", "classList"])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Show when={(local.messages?.length ?? 0) > 1}>
|
|
||||||
<div
|
|
||||||
{...others}
|
|
||||||
data-component="session-message-rail"
|
|
||||||
data-wide={local.wide ? "" : undefined}
|
|
||||||
classList={{
|
|
||||||
...(local.classList ?? {}),
|
|
||||||
[local.class ?? ""]: !!local.class,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div data-slot="session-message-rail-compact">
|
|
||||||
<MessageNav
|
|
||||||
messages={local.messages}
|
|
||||||
current={local.current}
|
|
||||||
onMessageSelect={local.onMessageSelect}
|
|
||||||
size="compact"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div data-slot="session-message-rail-full">
|
|
||||||
<MessageNav
|
|
||||||
messages={local.messages}
|
|
||||||
current={local.current}
|
|
||||||
onMessageSelect={local.onMessageSelect}
|
|
||||||
size={local.wide ? "normal" : "compact"}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user