|
|
@@ -9,14 +9,13 @@ import { Tooltip } from "@opencode-ai/ui/tooltip"
|
|
|
import { base64Encode } from "@opencode-ai/util/encode"
|
|
|
import { getFilename } from "@opencode-ai/util/path"
|
|
|
import { A, useNavigate, useParams } from "@solidjs/router"
|
|
|
-import { type Accessor, createEffect, createMemo, For, type JSX, on, onCleanup, Show } from "solid-js"
|
|
|
-import { createStore } from "solid-js/store"
|
|
|
+import { type Accessor, createMemo, For, type JSX, Match, onCleanup, Show, Switch } from "solid-js"
|
|
|
import { useGlobalSync } from "@/context/global-sync"
|
|
|
import { useLanguage } from "@/context/language"
|
|
|
import { getAvatarColors, type LocalProject, useLayout } from "@/context/layout"
|
|
|
import { useNotification } from "@/context/notification"
|
|
|
import { usePermission } from "@/context/permission"
|
|
|
-import { agentColor } from "@/utils/agent"
|
|
|
+import { messageAgentColor } from "@/utils/agent"
|
|
|
import { sessionPermissionRequest } from "../session/composer/session-request-tree"
|
|
|
import { hasProjectPermissions } from "./helpers"
|
|
|
|
|
|
@@ -102,94 +101,46 @@ const SessionRow = (props: {
|
|
|
warmPress: () => void
|
|
|
warmFocus: () => void
|
|
|
cancelHoverPrefetch: () => void
|
|
|
-}): JSX.Element => {
|
|
|
- const [slot, setSlot] = createStore({
|
|
|
- open: false,
|
|
|
- show: false,
|
|
|
- fade: false,
|
|
|
- })
|
|
|
-
|
|
|
- let f: number | undefined
|
|
|
- const clear = () => {
|
|
|
- if (f !== undefined) window.clearTimeout(f)
|
|
|
- f = undefined
|
|
|
- }
|
|
|
-
|
|
|
- onCleanup(clear)
|
|
|
- createEffect(
|
|
|
- on(
|
|
|
- () => props.isWorking(),
|
|
|
- (on, prev) => {
|
|
|
- clear()
|
|
|
- if (on) {
|
|
|
- setSlot({ open: true, show: true, fade: false })
|
|
|
- return
|
|
|
- }
|
|
|
- if (prev) {
|
|
|
- setSlot({ open: false, show: true, fade: true })
|
|
|
- f = window.setTimeout(() => setSlot({ show: false, fade: false }), 260)
|
|
|
- return
|
|
|
- }
|
|
|
- setSlot({ open: false, show: false, fade: false })
|
|
|
- },
|
|
|
- { defer: true },
|
|
|
- ),
|
|
|
- )
|
|
|
-
|
|
|
- return (
|
|
|
- <A
|
|
|
- href={`/${props.slug}/session/${props.session.id}`}
|
|
|
- class={`relative flex items-center min-w-0 text-left w-full focus:outline-none transition-[padding] ${props.mobile ? "pr-7" : ""} group-hover/session:pr-7 group-focus-within/session:pr-7 group-active/session:pr-7 ${props.dense ? "py-0.5" : "py-1"}`}
|
|
|
- onPointerDown={props.warmPress}
|
|
|
- onPointerEnter={props.warmHover}
|
|
|
- onPointerLeave={props.cancelHoverPrefetch}
|
|
|
- onFocus={props.warmFocus}
|
|
|
- onClick={() => {
|
|
|
- props.setHoverSession(undefined)
|
|
|
- if (props.sidebarOpened()) return
|
|
|
- props.clearHoverProjectSoon()
|
|
|
- }}
|
|
|
- >
|
|
|
- <Show when={!props.isWorking() && (props.hasPermissions() || props.hasError() || props.unseenCount() > 0)}>
|
|
|
- <div
|
|
|
- classList={{
|
|
|
- "absolute left-0 top-1/2 -translate-y-1/2 size-1.5 rounded-full": true,
|
|
|
- "bg-surface-warning-strong": props.hasPermissions(),
|
|
|
- "bg-text-diff-delete-base": !props.hasPermissions() && props.hasError(),
|
|
|
- "bg-text-interactive-base": !props.hasPermissions() && !props.hasError() && props.unseenCount() > 0,
|
|
|
- }}
|
|
|
- aria-hidden="true"
|
|
|
- />
|
|
|
- </Show>
|
|
|
-
|
|
|
- <div class="flex items-center min-w-0 grow-1">
|
|
|
- <div
|
|
|
- class="shrink-0 flex items-center justify-center overflow-hidden transition-[width,margin] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]"
|
|
|
- style={{
|
|
|
- width: slot.open ? "16px" : "0px",
|
|
|
- "margin-right": slot.open ? "8px" : "0px",
|
|
|
- }}
|
|
|
- aria-hidden="true"
|
|
|
- >
|
|
|
- <Show when={slot.show}>
|
|
|
- <div
|
|
|
- class="transition-opacity duration-200 ease-out"
|
|
|
- classList={{
|
|
|
- "opacity-0": slot.fade,
|
|
|
- }}
|
|
|
- >
|
|
|
- <Spinner class="size-4" style={{ color: props.tint() ?? "var(--icon-interactive-base)" }} />
|
|
|
- </div>
|
|
|
- </Show>
|
|
|
- </div>
|
|
|
-
|
|
|
- <span class="text-14-regular text-text-strong grow-1 min-w-0 overflow-hidden text-ellipsis truncate">
|
|
|
- {props.session.title}
|
|
|
- </span>
|
|
|
+}): JSX.Element => (
|
|
|
+ <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] ${props.mobile ? "pr-7" : ""} group-hover/session:pr-7 group-focus-within/session:pr-7 group-active/session:pr-7 ${props.dense ? "py-0.5" : "py-1"}`}
|
|
|
+ onPointerDown={props.warmPress}
|
|
|
+ onPointerEnter={props.warmHover}
|
|
|
+ onPointerLeave={props.cancelHoverPrefetch}
|
|
|
+ onFocus={props.warmFocus}
|
|
|
+ onClick={() => {
|
|
|
+ props.setHoverSession(undefined)
|
|
|
+ if (props.sidebarOpened()) return
|
|
|
+ props.clearHoverProjectSoon()
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div class="flex items-center gap-1 w-full">
|
|
|
+ <div
|
|
|
+ class="shrink-0 size-6 flex items-center justify-center"
|
|
|
+ style={{ color: props.tint() ?? "var(--icon-interactive-base)" }}
|
|
|
+ >
|
|
|
+ <Switch fallback={<Icon name="dash" size="small" class="text-icon-weak" />}>
|
|
|
+ <Match when={props.isWorking()}>
|
|
|
+ <Spinner class="size-[15px]" />
|
|
|
+ </Match>
|
|
|
+ <Match when={props.hasPermissions()}>
|
|
|
+ <div class="size-1.5 rounded-full bg-surface-warning-strong" />
|
|
|
+ </Match>
|
|
|
+ <Match when={props.hasError()}>
|
|
|
+ <div class="size-1.5 rounded-full bg-text-diff-delete-base" />
|
|
|
+ </Match>
|
|
|
+ <Match when={props.unseenCount() > 0}>
|
|
|
+ <div class="size-1.5 rounded-full bg-text-interactive-base" />
|
|
|
+ </Match>
|
|
|
+ </Switch>
|
|
|
</div>
|
|
|
- </A>
|
|
|
- )
|
|
|
-}
|
|
|
+ <span class="text-14-regular text-text-strong grow-1 min-w-0 overflow-hidden text-ellipsis truncate">
|
|
|
+ {props.session.title}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </A>
|
|
|
+)
|
|
|
|
|
|
const SessionHoverPreview = (props: {
|
|
|
mobile?: boolean
|
|
|
@@ -268,19 +219,7 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => {
|
|
|
})
|
|
|
|
|
|
const tint = createMemo(() => {
|
|
|
- const messages = sessionStore.message[props.session.id]
|
|
|
- if (!messages) return undefined
|
|
|
- let user: Message | undefined
|
|
|
- for (let i = messages.length - 1; i >= 0; i--) {
|
|
|
- const message = messages[i]
|
|
|
- if (message.role !== "user") continue
|
|
|
- user = message
|
|
|
- break
|
|
|
- }
|
|
|
- if (!user?.agent) return undefined
|
|
|
-
|
|
|
- const agent = sessionStore.agent.find((a) => a.name === user.agent)
|
|
|
- return agentColor(user.agent, agent?.color)
|
|
|
+ return messageAgentColor(sessionStore.message[props.session.id], sessionStore.agent)
|
|
|
})
|
|
|
|
|
|
const hoverMessages = createMemo(() =>
|
|
|
@@ -359,7 +298,7 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => {
|
|
|
return (
|
|
|
<div
|
|
|
data-session-id={props.session.id}
|
|
|
- class="group/session relative w-full rounded-md cursor-default pl-3 pr-3 transition-colors
|
|
|
+ class="group/session relative w-full rounded-md cursor-default pl-2 pr-3 transition-colors
|
|
|
hover:bg-surface-raised-base-hover [&:has(:focus-visible)]:bg-surface-raised-base-hover has-[[data-expanded]]:bg-surface-raised-base-hover has-[.active]:bg-surface-base-active"
|
|
|
>
|
|
|
<Show
|