summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-10-29 16:04:30 -0500
committerAdam <[email protected]>2025-10-29 16:04:34 -0500
commit3210df74287f8c2afa4bb382d1da420c2826c2b7 (patch)
treed762679b36178da5b59dca13616646f44866f976 /packages
parentcdeb82e9ca0213960202447896493c8d18cb867b (diff)
downloadopencode-3210df74287f8c2afa4bb382d1da420c2826c2b7.tar.gz
opencode-3210df74287f8c2afa4bb382d1da420c2826c2b7.zip
wip: desktop work
Diffstat (limited to 'packages')
-rw-r--r--packages/desktop/src/pages/index.tsx122
-rw-r--r--packages/ui/src/styles/tailwind/index.css20
2 files changed, 114 insertions, 28 deletions
diff --git a/packages/desktop/src/pages/index.tsx b/packages/desktop/src/pages/index.tsx
index 341da2d95..5216c4272 100644
--- a/packages/desktop/src/pages/index.tsx
+++ b/packages/desktop/src/pages/index.tsx
@@ -36,6 +36,7 @@ import { ProgressCircle } from "@/components/progress-circle"
import { Message, Part } from "@/components/message"
import { type AssistantMessage as AssistantMessageType } from "@opencode-ai/sdk"
import { DiffChanges } from "@/components/diff-changes"
+import { Markdown } from "@/components/markdown"
export default function Page() {
const local = useLocal()
@@ -491,8 +492,8 @@ export default function Page() {
</Show>
</div>
</div>
- <Tabs.Content value="chat" class="select-text flex flex-col flex-1 min-h-0 overflow-y-hidden">
- <div class="px-6 pt-12 max-w-[904px] w-full mx-auto flex flex-col flex-1 min-h-0">
+ <Tabs.Content value="chat" class="@container select-text flex flex-col flex-1 min-h-0 overflow-y-hidden">
+ <div class="relative px-6 pt-12 max-w-2xl w-full mx-auto flex flex-col flex-1 min-h-0">
<Show
when={local.session.active()}
fallback={
@@ -519,9 +520,12 @@ export default function Page() {
>
{(activeSession) => (
<div class="pt-3 flex flex-col flex-1 min-h-0">
- <div class="flex items-start gap-8 flex-1 min-h-0">
+ <div class="flex-1 min-h-0">
<Show when={local.session.userMessages().length > 1}>
- <ul role="list" class="w-60 shrink-0 flex flex-col items-start gap-1">
+ <ul
+ role="list"
+ class="absolute right-full mr-8 hidden w-60 shrink-0 @7xl:flex flex-col items-start gap-1"
+ >
<For each={local.session.userMessages()}>
{(message) => {
const countLines = (text: string) => {
@@ -648,16 +652,12 @@ export default function Page() {
(m) => m.role === "assistant" && m.parentID == message.id,
) as AssistantMessageType[]
})
- const working = createMemo(() => {
- const last = assistantMessages()[assistantMessages().length - 1]
- if (!last) return false
- return !last.time.completed
- })
+ const working = createMemo(() => !summary())
return (
<div
data-message={message.id}
- class="flex flex-col items-start self-stretch gap-8 min-h-[calc(100vh-15rem)]"
+ class="flex flex-col items-start self-stretch gap-8 min-h-screen"
>
{/* Title */}
<div class="py-2 flex flex-col items-start gap-2 self-stretch sticky top-0 bg-background-stronger">
@@ -675,7 +675,9 @@ export default function Page() {
<div class="w-full flex flex-col gap-6 items-start self-stretch">
<div class="flex flex-col items-start gap-1 self-stretch">
<h2 class="text-12-medium text-text-weak">Summary</h2>
- <div class="text-14-regular text-text-base self-stretch">{summary()}</div>
+ <Show when={summary()}>
+ <Markdown text={summary()!} />
+ </Show>
</div>
<Accordion class="w-full" multiple>
<For each={message.summary?.diffs || []}>
@@ -729,14 +731,85 @@ export default function Page() {
<div class="w-full">
<Switch>
<Match when={working()}>
- <div class="w-full flex flex-col-reverse items-start self-stretch gap-6 max-h-30 overflow-y-auto no-scrollbar pointer-events-none mask-alpha mask-y-from-66% mask-y-from-background-base mask-y-to-transparent">
- <For each={assistantMessages()?.toReversed()}>
- {(assistantMessage) => {
- const parts = createMemo(() => sync.data.part[assistantMessage.id])
- return <Message message={assistantMessage} parts={parts()} />
- }}
- </For>
- </div>
+ {(_) => {
+ const items = createMemo(() =>
+ assistantMessages().flatMap((m) => sync.data.part[m.id]),
+ )
+ const finishedItems = createMemo(() =>
+ items().filter(
+ (p) =>
+ (p?.type === "text" && p.time?.end) ||
+ (p?.type === "reasoning" && p.time?.end) ||
+ (p?.type === "tool" && p.state.status === "completed"),
+ ),
+ )
+
+ const MINIMUM_DELAY = 800
+ 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) * 48 - 8}px`
+ })
+
+ return (
+ <div class="flex flex-col gap-3">
+ <div
+ class="h-36 overflow-hidden pointer-events-none
+ mask-alpha mask-y-from-66% mask-y-from-background-base mask-y-to-transparent"
+ >
+ <div
+ class="w-full flex flex-col items-start self-stretch gap-2 py-10
+ transform transition-transform duration-500 ease-[cubic-bezier(0.22,1,0.36,1)]"
+ style={{ transform: `translateY(${translateY()})` }}
+ >
+ <For each={finishedItems()}>
+ {(part) => {
+ const message = createMemo(() =>
+ sync.data.message[part.sessionID].find(
+ (m) => m.id === part.messageID,
+ ),
+ )
+ return (
+ <div class="h-10 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>
+ </div>
+ )
+ }}
</Match>
<Match when={!working()}>
<Collapsible variant="ghost" open={expanded()} onOpenChange={setExpanded}>
@@ -752,7 +825,7 @@ export default function Page() {
</div>
</Collapsible.Trigger>
<Collapsible.Content>
- <div class="w-full flex flex-col-reverse items-start self-stretch gap-8">
+ <div class="w-full flex flex-col items-start self-stretch gap-8">
<For each={assistantMessages()}>
{(assistantMessage) => {
const parts = createMemo(
@@ -807,14 +880,7 @@ export default function Page() {
})()}
</DragOverlay>
</DragDropProvider>
- <div
- classList={{
- "absolute inset-x-0 px-6 max-w-[904px] flex flex-col justify-center items-center z-50 mx-auto": true,
- "bottom-8": true,
- // "bottom-8": !!local.session.active(),
- // "bottom-1/2 translate-y-1/2": !local.session.active(),
- }}
- >
+ <div class="absolute inset-x-0 px-6 max-w-2xl flex flex-col justify-center items-center z-50 mx-auto bottom-8">
<PromptInput
ref={(el) => {
inputRef = el
diff --git a/packages/ui/src/styles/tailwind/index.css b/packages/ui/src/styles/tailwind/index.css
index e8e9641b4..76d8c7d3e 100644
--- a/packages/ui/src/styles/tailwind/index.css
+++ b/packages/ui/src/styles/tailwind/index.css
@@ -11,6 +11,26 @@
--spacing: 0.25rem;
/* --spacing: var(--spacing); */
+ --breakpoint-sm: 40rem;
+ --breakpoint-md: 48rem;
+ --breakpoint-lg: 64rem;
+ --breakpoint-xl: 80rem;
+ --breakpoint-2xl: 96rem;
+
+ --container-3xs: 16rem;
+ --container-2xs: 18rem;
+ --container-xs: 20rem;
+ --container-sm: 24rem;
+ --container-md: 28rem;
+ --container-lg: 32rem;
+ --container-xl: 36rem;
+ --container-2xl: 42rem;
+ --container-3xl: 48rem;
+ --container-4xl: 56rem;
+ --container-5xl: 64rem;
+ --container-6xl: 72rem;
+ --container-7xl: 80rem;
+
--font-sans: var(--font-family-sans);
--font-sans--font-feature-settings: var(--font-family-sans--font-feature-settings);
--font-mono: var(--font-family-mono);