mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-30 05:43:55 +00:00
fix(app): restore sidebar dash and sync session spinner colors (#17384)
This commit is contained in:
parent
c7a52b6a2d
commit
536abea2e2
@ -16,9 +16,11 @@ import { useLanguage } from "@/context/language"
|
|||||||
import { useLayout } from "@/context/layout"
|
import { useLayout } from "@/context/layout"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
import { useServer } from "@/context/server"
|
import { useServer } from "@/context/server"
|
||||||
|
import { useSync } from "@/context/sync"
|
||||||
import { useTerminal } from "@/context/terminal"
|
import { useTerminal } from "@/context/terminal"
|
||||||
import { focusTerminalById } from "@/pages/session/helpers"
|
import { focusTerminalById } from "@/pages/session/helpers"
|
||||||
import { useSessionLayout } from "@/pages/session/session-layout"
|
import { useSessionLayout } from "@/pages/session/session-layout"
|
||||||
|
import { messageAgentColor } from "@/utils/agent"
|
||||||
import { decode64 } from "@/utils/base64"
|
import { decode64 } from "@/utils/base64"
|
||||||
import { Persist, persisted } from "@/utils/persist"
|
import { Persist, persisted } from "@/utils/persist"
|
||||||
import { StatusPopover } from "../status-popover"
|
import { StatusPopover } from "../status-popover"
|
||||||
@ -132,6 +134,7 @@ export function SessionHeader() {
|
|||||||
const server = useServer()
|
const server = useServer()
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
const sync = useSync()
|
||||||
const terminal = useTerminal()
|
const terminal = useTerminal()
|
||||||
const { params, view } = useSessionLayout()
|
const { params, view } = useSessionLayout()
|
||||||
|
|
||||||
@ -218,6 +221,9 @@ export function SessionHeader() {
|
|||||||
({ id: "finder", label: fileManager().label, icon: fileManager().icon } as const),
|
({ id: "finder", label: fileManager().label, icon: fileManager().icon } as const),
|
||||||
)
|
)
|
||||||
const opening = createMemo(() => openRequest.app !== undefined)
|
const opening = createMemo(() => openRequest.app !== undefined)
|
||||||
|
const tint = createMemo(() =>
|
||||||
|
messageAgentColor(params.id ? sync.data.message[params.id] : undefined, sync.data.agent),
|
||||||
|
)
|
||||||
|
|
||||||
const selectApp = (app: OpenApp) => {
|
const selectApp = (app: OpenApp) => {
|
||||||
if (!options().some((item) => item.id === app)) return
|
if (!options().some((item) => item.id === app)) return
|
||||||
@ -330,7 +336,7 @@ export function SessionHeader() {
|
|||||||
>
|
>
|
||||||
<div class="flex size-5 shrink-0 items-center justify-center [&_[data-component=app-icon]]:size-5">
|
<div class="flex size-5 shrink-0 items-center justify-center [&_[data-component=app-icon]]:size-5">
|
||||||
<Show when={opening()} fallback={<AppIcon id={current().icon} />}>
|
<Show when={opening()} fallback={<AppIcon id={current().icon} />}>
|
||||||
<Spinner class="size-3.5 text-icon-base" />
|
<Spinner class="size-3.5" style={{ color: tint() ?? "var(--icon-base)" }} />
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-12-regular text-text-strong">{language.t("common.open")}</span>
|
<span class="text-12-regular text-text-strong">{language.t("common.open")}</span>
|
||||||
|
|||||||
@ -9,14 +9,13 @@ import { Tooltip } from "@opencode-ai/ui/tooltip"
|
|||||||
import { base64Encode } from "@opencode-ai/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { getFilename } from "@opencode-ai/util/path"
|
import { getFilename } from "@opencode-ai/util/path"
|
||||||
import { A, useNavigate, useParams } from "@solidjs/router"
|
import { A, useNavigate, useParams } from "@solidjs/router"
|
||||||
import { type Accessor, createEffect, createMemo, For, type JSX, on, onCleanup, Show } from "solid-js"
|
import { type Accessor, createMemo, For, type JSX, Match, onCleanup, Show, Switch } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
|
||||||
import { useGlobalSync } from "@/context/global-sync"
|
import { useGlobalSync } from "@/context/global-sync"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { getAvatarColors, type LocalProject, useLayout } from "@/context/layout"
|
import { getAvatarColors, type LocalProject, useLayout } from "@/context/layout"
|
||||||
import { useNotification } from "@/context/notification"
|
import { useNotification } from "@/context/notification"
|
||||||
import { usePermission } from "@/context/permission"
|
import { usePermission } from "@/context/permission"
|
||||||
import { agentColor } from "@/utils/agent"
|
import { messageAgentColor } from "@/utils/agent"
|
||||||
import { sessionPermissionRequest } from "../session/composer/session-request-tree"
|
import { sessionPermissionRequest } from "../session/composer/session-request-tree"
|
||||||
import { hasProjectPermissions } from "./helpers"
|
import { hasProjectPermissions } from "./helpers"
|
||||||
|
|
||||||
@ -102,94 +101,46 @@ const SessionRow = (props: {
|
|||||||
warmPress: () => void
|
warmPress: () => void
|
||||||
warmFocus: () => void
|
warmFocus: () => void
|
||||||
cancelHoverPrefetch: () => void
|
cancelHoverPrefetch: () => void
|
||||||
}): JSX.Element => {
|
}): JSX.Element => (
|
||||||
const [slot, setSlot] = createStore({
|
<A
|
||||||
open: false,
|
href={`/${props.slug}/session/${props.session.id}`}
|
||||||
show: false,
|
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"}`}
|
||||||
fade: false,
|
onPointerDown={props.warmPress}
|
||||||
})
|
onPointerEnter={props.warmHover}
|
||||||
|
onPointerLeave={props.cancelHoverPrefetch}
|
||||||
let f: number | undefined
|
onFocus={props.warmFocus}
|
||||||
const clear = () => {
|
onClick={() => {
|
||||||
if (f !== undefined) window.clearTimeout(f)
|
props.setHoverSession(undefined)
|
||||||
f = undefined
|
if (props.sidebarOpened()) return
|
||||||
}
|
props.clearHoverProjectSoon()
|
||||||
|
}}
|
||||||
onCleanup(clear)
|
>
|
||||||
createEffect(
|
<div class="flex items-center gap-1 w-full">
|
||||||
on(
|
<div
|
||||||
() => props.isWorking(),
|
class="shrink-0 size-6 flex items-center justify-center"
|
||||||
(on, prev) => {
|
style={{ color: props.tint() ?? "var(--icon-interactive-base)" }}
|
||||||
clear()
|
>
|
||||||
if (on) {
|
<Switch fallback={<Icon name="dash" size="small" class="text-icon-weak" />}>
|
||||||
setSlot({ open: true, show: true, fade: false })
|
<Match when={props.isWorking()}>
|
||||||
return
|
<Spinner class="size-[15px]" />
|
||||||
}
|
</Match>
|
||||||
if (prev) {
|
<Match when={props.hasPermissions()}>
|
||||||
setSlot({ open: false, show: true, fade: true })
|
<div class="size-1.5 rounded-full bg-surface-warning-strong" />
|
||||||
f = window.setTimeout(() => setSlot({ show: false, fade: false }), 260)
|
</Match>
|
||||||
return
|
<Match when={props.hasError()}>
|
||||||
}
|
<div class="size-1.5 rounded-full bg-text-diff-delete-base" />
|
||||||
setSlot({ open: false, show: false, fade: false })
|
</Match>
|
||||||
},
|
<Match when={props.unseenCount() > 0}>
|
||||||
{ defer: true },
|
<div class="size-1.5 rounded-full bg-text-interactive-base" />
|
||||||
),
|
</Match>
|
||||||
)
|
</Switch>
|
||||||
|
|
||||||
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>
|
|
||||||
</div>
|
</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: {
|
const SessionHoverPreview = (props: {
|
||||||
mobile?: boolean
|
mobile?: boolean
|
||||||
@ -268,19 +219,7 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const tint = createMemo(() => {
|
const tint = createMemo(() => {
|
||||||
const messages = sessionStore.message[props.session.id]
|
return messageAgentColor(sessionStore.message[props.session.id], sessionStore.agent)
|
||||||
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)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const hoverMessages = createMemo(() =>
|
const hoverMessages = createMemo(() =>
|
||||||
@ -359,7 +298,7 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => {
|
|||||||
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 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"
|
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
|
<Show
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import { usePlatform } from "@/context/platform"
|
|||||||
import { useSettings } from "@/context/settings"
|
import { useSettings } from "@/context/settings"
|
||||||
import { useSDK } from "@/context/sdk"
|
import { useSDK } from "@/context/sdk"
|
||||||
import { useSync } from "@/context/sync"
|
import { useSync } from "@/context/sync"
|
||||||
|
import { messageAgentColor } from "@/utils/agent"
|
||||||
import { parseCommentNote, readCommentMetadata } from "@/utils/comment-note"
|
import { parseCommentNote, readCommentMetadata } from "@/utils/comment-note"
|
||||||
|
|
||||||
type MessageComment = {
|
type MessageComment = {
|
||||||
@ -246,6 +247,7 @@ export function MessageTimeline(props: {
|
|||||||
return sync.data.session_status[id] ?? idle
|
return sync.data.session_status[id] ?? idle
|
||||||
})
|
})
|
||||||
const working = createMemo(() => !!pending() || sessionStatus().type !== "idle")
|
const working = createMemo(() => !!pending() || sessionStatus().type !== "idle")
|
||||||
|
const tint = createMemo(() => messageAgentColor(sessionMessages(), sync.data.agent))
|
||||||
|
|
||||||
const [slot, setSlot] = createStore({
|
const [slot, setSlot] = createStore({
|
||||||
open: false,
|
open: false,
|
||||||
@ -689,7 +691,7 @@ export function MessageTimeline(props: {
|
|||||||
"opacity-0": slot.fade,
|
"opacity-0": slot.fade,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Spinner class="size-4" style={{ color: "var(--icon-interactive-base)" }} />
|
<Spinner class="size-4" style={{ color: tint() ?? "var(--icon-interactive-base)" }} />
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -9,3 +9,15 @@ export function agentColor(name: string, custom?: string) {
|
|||||||
if (custom) return custom
|
if (custom) return custom
|
||||||
return defaults[name] ?? defaults[name.toLowerCase()]
|
return defaults[name] ?? defaults[name.toLowerCase()]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function messageAgentColor(
|
||||||
|
list: readonly { role: string; agent?: string }[] | undefined,
|
||||||
|
agents: readonly { name: string; color?: string }[],
|
||||||
|
) {
|
||||||
|
if (!list) return undefined
|
||||||
|
for (let i = list.length - 1; i >= 0; i--) {
|
||||||
|
const item = list[i]
|
||||||
|
if (item.role !== "user" || !item.agent) continue
|
||||||
|
return agentColor(item.agent, agents.find((agent) => agent.name === item.agent)?.color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user