wip: desktop work

This commit is contained in:
Adam
2025-10-31 09:45:57 -05:00
parent 485135cf5c
commit 0ac943de90
5 changed files with 80 additions and 5 deletions

View File

@@ -13,6 +13,7 @@ import {
DiffChanges, DiffChanges,
ProgressCircle, ProgressCircle,
Message, Message,
Typewriter,
} from "@opencode-ai/ui" } from "@opencode-ai/ui"
import { FileIcon } from "@/ui" import { FileIcon } from "@/ui"
import FileTree from "@/components/file-tree" import FileTree from "@/components/file-tree"
@@ -544,7 +545,6 @@ export default function Page() {
<For each={local.session.userMessages()}> <For each={local.session.userMessages()}>
{(message) => { {(message) => {
const diffs = createMemo(() => message.summary?.diffs ?? []) const diffs = createMemo(() => message.summary?.diffs ?? [])
return ( return (
<li <li
class="group/li flex items-center gap-x-2 py-1 self-stretch cursor-default" class="group/li flex items-center gap-x-2 py-1 self-stretch cursor-default"
@@ -570,9 +570,9 @@ export default function Page() {
<div class="flex flex-col items-start gap-50 pb-50"> <div class="flex flex-col items-start gap-50 pb-50">
<For each={local.session.userMessages()}> <For each={local.session.userMessages()}>
{(message) => { {(message) => {
const [initialized, setInitialized] = createSignal(!!message.summary?.title)
const [expanded, setExpanded] = createSignal(false) const [expanded, setExpanded] = createSignal(false)
const parts = createMemo(() => sync.data.part[message.id]) const parts = createMemo(() => sync.data.part[message.id])
const prompt = createMemo(() => local.session.getMessageText(message))
const title = createMemo(() => message.summary?.title) const title = createMemo(() => message.summary?.title)
const summary = createMemo(() => message.summary?.body) const summary = createMemo(() => message.summary?.body)
const assistantMessages = createMemo(() => { const assistantMessages = createMemo(() => {
@@ -581,6 +581,9 @@ export default function Page() {
) as AssistantMessageType[] ) as AssistantMessageType[]
}) })
const working = createMemo(() => !summary()) const working = createMemo(() => !summary())
createEffect(() => {
setTimeout(() => setInitialized(!!title()), 10_000)
})
return ( return (
<div <div
@@ -589,9 +592,11 @@ export default function Page() {
> >
{/* Title */} {/* Title */}
<div class="py-2 flex flex-col items-start gap-2 self-stretch sticky top-0 bg-background-stronger z-10"> <div class="py-2 flex flex-col items-start gap-2 self-stretch sticky top-0 bg-background-stronger z-10">
<h1 class="text-14-medium text-text-strong overflow-hidden text-ellipsis min-w-0"> <div class="text-14-medium text-text-strong overflow-hidden text-ellipsis min-w-0">
{title() ?? prompt()} <Show when={initialized()} fallback={<Typewriter as="h1" text={title()} />}>
</h1> <h1>{title()}</h1>
</Show>
</div>
</div> </div>
<Show when={title}> <Show when={title}>
<div class="-mt-8"> <div class="-mt-8">

View File

@@ -20,6 +20,7 @@ export * from "./select-dialog"
export * from "./tabs" export * from "./tabs"
export * from "./basic-tool" export * from "./basic-tool"
export * from "./tooltip" export * from "./tooltip"
export * from "./typewriter"
export * from "../context/helper" export * from "../context/helper"
export * from "../context/shiki" export * from "../context/shiki"

View File

@@ -0,0 +1,14 @@
@keyframes blink {
0%,
50% {
opacity: 1;
}
51%,
100% {
opacity: 0;
}
}
.blinking-cursor {
animation: blink 1s step-end infinite;
}

View File

@@ -0,0 +1,54 @@
import { createEffect, Show, type ValidComponent } from "solid-js"
import { createStore } from "solid-js/store"
import { Dynamic } from "solid-js/web"
export const Typewriter = <T extends ValidComponent = "p">(props: {
text?: string
class?: string
as?: T
}) => {
const [store, setStore] = createStore({
typing: false,
displayed: "",
cursor: true,
})
createEffect(() => {
const text = props.text
if (!text) return
let i = 0
setStore("typing", true)
setStore("displayed", "")
setStore("cursor", true)
const getTypingDelay = () => {
const random = Math.random()
if (random < 0.05) return 150 + Math.random() * 100
if (random < 0.15) return 80 + Math.random() * 60
return 30 + Math.random() * 50
}
const type = () => {
if (i < text.length) {
setStore("displayed", text.slice(0, i + 1))
i++
setTimeout(type, getTypingDelay())
} else {
setStore("typing", false)
setTimeout(() => setStore("cursor", false), 2000)
}
}
setTimeout(type, 200)
})
return (
<Dynamic component={props.as || "p"} class={props.class}>
{store.displayed}
<Show when={store.cursor}>
<span classList={{ "blinking-cursor": !store.typing }}></span>
</Show>
</Dynamic>
)
}

View File

@@ -25,5 +25,6 @@
@import "../components/select-dialog.css" layer(components); @import "../components/select-dialog.css" layer(components);
@import "../components/tabs.css" layer(components); @import "../components/tabs.css" layer(components);
@import "../components/tooltip.css" layer(components); @import "../components/tooltip.css" layer(components);
@import "../components/typewriter.css" layer(components);
@import "./utilities.css" layer(utilities); @import "./utilities.css" layer(utilities);