wip: desktop work

This commit is contained in:
Adam
2025-10-31 11:54:27 -05:00
parent 36b48a44ac
commit ffc889b99e
9 changed files with 174 additions and 175 deletions

View File

@@ -0,0 +1,82 @@
import { For, Match, Switch, createEffect, createMemo, createSignal, onCleanup } from "solid-js"
import { Part } from "@opencode-ai/ui"
import { useSync } from "@/context/sync"
import type { AssistantMessage as AssistantMessageType } from "@opencode-ai/sdk"
export function MessageProgress(props: { assistantMessages: () => AssistantMessageType[] }) {
const sync = useSync()
const items = createMemo(() => props.assistantMessages().flatMap((m) => sync.data.part[m.id]))
const finishedItems = createMemo(() => [
"",
"",
"Loading...",
...items().filter(
(p) =>
p?.type === "text" ||
(p?.type === "reasoning" && p.time?.end) ||
(p?.type === "tool" && p.state.status === "completed"),
),
"",
])
const MINIMUM_DELAY = 400
const [visibleCount, setVisibleCount] = createSignal(1)
createEffect(() => {
const total = finishedItems().length
if (total > visibleCount()) {
const timer = setTimeout(() => {
setVisibleCount((prev) => prev + 1)
}, MINIMUM_DELAY)
onCleanup(() => clearTimeout(timer))
} else if (total < visibleCount()) {
setVisibleCount(total)
}
})
const translateY = createMemo(() => {
const total = visibleCount()
if (total < 2) return "0px"
return `-${(total - 2) * 40 - 8}px`
})
return (
<div
class="h-30 overflow-hidden pointer-events-none
mask-alpha mask-t-from-33% mask-t-from-background-base mask-t-to-transparent
mask-b-from-90% mask-b-from-background-base mask-b-to-transparent"
>
<div
class="w-full flex flex-col items-start self-stretch gap-2 py-8
transform transition-transform duration-500 ease-[cubic-bezier(0.22,1,0.36,1)]"
style={{ transform: `translateY(${translateY()})` }}
>
<For each={finishedItems()}>
{(part) => {
if (typeof part === "string") return <div class="h-8 flex items-center w-full">{part}</div>
const message = createMemo(() => sync.data.message[part.sessionID].find((m) => m.id === part.messageID))
return (
<div class="h-8 flex items-center w-full">
<Switch>
<Match when={part.type === "text" && part}>
{(p) => (
<div
textContent={p().text}
class="text-12-regular text-text-base whitespace-nowrap truncate w-full"
/>
)}
</Match>
<Match when={part.type === "reasoning" && part}>
{(p) => <Part message={message()!} part={p()} />}
</Match>
<Match when={part.type === "tool" && part}>{(p) => <Part message={message()!} part={p()} />}</Match>
</Switch>
</div>
)
}}
</For>
</div>
</div>
)
}

View File

@@ -0,0 +1,39 @@
import { ComponentProps, For } from "solid-js"
export function Spinner(props: { class?: string; classList?: ComponentProps<"div">["classList"] }) {
const squares = Array.from({ length: 16 }, (_, i) => ({
id: i,
x: (i % 4) * 4,
y: Math.floor(i / 4) * 4,
delay: Math.random() * 3,
duration: 2 + Math.random() * 2,
}))
return (
<svg
viewBox="0 0 15 15"
classList={{
"size-4": true,
...(props.classList ?? {}),
[props.class ?? ""]: !!props.class,
}}
fill="currentColor"
>
<For each={squares}>
{(square) => (
<rect
x={square.x}
y={square.y}
width="3"
height="3"
rx="1"
style={{
animation: `pulse-opacity ${square.duration}s ease-in-out infinite`,
"animation-delay": `${square.delay}s`,
}}
/>
)}
</For>
</svg>
)
}