diff options
| author | Adam <[email protected]> | 2025-10-31 09:45:57 -0500 |
|---|---|---|
| committer | Adam <[email protected]> | 2025-10-31 09:45:57 -0500 |
| commit | 0ac943de909fb5a685a608d7894248cdd3a9e129 (patch) | |
| tree | 6d2e0c0cb1a44de4f53e74a7002622842dab7591 | |
| parent | 485135cf5c4af64d1449aa3cadcdd0aef92201a3 (diff) | |
| download | opencode-0ac943de909fb5a685a608d7894248cdd3a9e129.tar.gz opencode-0ac943de909fb5a685a608d7894248cdd3a9e129.zip | |
wip: desktop work
| -rw-r--r-- | packages/desktop/src/pages/index.tsx | 15 | ||||
| -rw-r--r-- | packages/ui/src/components/index.ts | 1 | ||||
| -rw-r--r-- | packages/ui/src/components/typewriter.css | 14 | ||||
| -rw-r--r-- | packages/ui/src/components/typewriter.tsx | 54 | ||||
| -rw-r--r-- | packages/ui/src/styles/index.css | 1 |
5 files changed, 80 insertions, 5 deletions
diff --git a/packages/desktop/src/pages/index.tsx b/packages/desktop/src/pages/index.tsx index 5237d78bb..01fe4b508 100644 --- a/packages/desktop/src/pages/index.tsx +++ b/packages/desktop/src/pages/index.tsx @@ -13,6 +13,7 @@ import { DiffChanges, ProgressCircle, Message, + Typewriter, } from "@opencode-ai/ui" import { FileIcon } from "@/ui" import FileTree from "@/components/file-tree" @@ -544,7 +545,6 @@ export default function Page() { <For each={local.session.userMessages()}> {(message) => { const diffs = createMemo(() => message.summary?.diffs ?? []) - return ( <li 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"> <For each={local.session.userMessages()}> {(message) => { + const [initialized, setInitialized] = createSignal(!!message.summary?.title) const [expanded, setExpanded] = createSignal(false) const parts = createMemo(() => sync.data.part[message.id]) - const prompt = createMemo(() => local.session.getMessageText(message)) const title = createMemo(() => message.summary?.title) const summary = createMemo(() => message.summary?.body) const assistantMessages = createMemo(() => { @@ -581,6 +581,9 @@ export default function Page() { ) as AssistantMessageType[] }) const working = createMemo(() => !summary()) + createEffect(() => { + setTimeout(() => setInitialized(!!title()), 10_000) + }) return ( <div @@ -589,9 +592,11 @@ export default function Page() { > {/* Title */} <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"> - {title() ?? prompt()} - </h1> + <div class="text-14-medium text-text-strong overflow-hidden text-ellipsis min-w-0"> + <Show when={initialized()} fallback={<Typewriter as="h1" text={title()} />}> + <h1>{title()}</h1> + </Show> + </div> </div> <Show when={title}> <div class="-mt-8"> diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts index 115e5f14f..499f2e27f 100644 --- a/packages/ui/src/components/index.ts +++ b/packages/ui/src/components/index.ts @@ -20,6 +20,7 @@ export * from "./select-dialog" export * from "./tabs" export * from "./basic-tool" export * from "./tooltip" +export * from "./typewriter" export * from "../context/helper" export * from "../context/shiki" diff --git a/packages/ui/src/components/typewriter.css b/packages/ui/src/components/typewriter.css new file mode 100644 index 000000000..e978312a9 --- /dev/null +++ b/packages/ui/src/components/typewriter.css @@ -0,0 +1,14 @@ +@keyframes blink { + 0%, + 50% { + opacity: 1; + } + 51%, + 100% { + opacity: 0; + } +} + +.blinking-cursor { + animation: blink 1s step-end infinite; +} diff --git a/packages/ui/src/components/typewriter.tsx b/packages/ui/src/components/typewriter.tsx new file mode 100644 index 000000000..9adb267ad --- /dev/null +++ b/packages/ui/src/components/typewriter.tsx @@ -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> + ) +} diff --git a/packages/ui/src/styles/index.css b/packages/ui/src/styles/index.css index cea5a082d..146d957e2 100644 --- a/packages/ui/src/styles/index.css +++ b/packages/ui/src/styles/index.css @@ -25,5 +25,6 @@ @import "../components/select-dialog.css" layer(components); @import "../components/tabs.css" layer(components); @import "../components/tooltip.css" layer(components); +@import "../components/typewriter.css" layer(components); @import "./utilities.css" layer(utilities); |
