diff --git a/packages/desktop/src/pages/index.tsx b/packages/desktop/src/pages/index.tsx index 5237d78b..01fe4b50 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() { {(message) => { const diffs = createMemo(() => message.summary?.diffs ?? []) - return (
  • {(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 (
    {/* Title */}
    -

    - {title() ?? prompt()} -

    +
    + }> +

    {title()}

    +
    +
    diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts index 115e5f14..499f2e27 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 00000000..e978312a --- /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 00000000..9adb267a --- /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 = (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 ( + + {store.displayed} + + + + + ) +} diff --git a/packages/ui/src/styles/index.css b/packages/ui/src/styles/index.css index cea5a082..146d957e 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);