summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-10-29 14:08:52 -0500
committerAdam <[email protected]>2025-10-29 16:04:34 -0500
commitcdeb82e9ca0213960202447896493c8d18cb867b (patch)
tree931a7ea706760691ae06b2b90c06729a0b943dac
parenta9cae7b33563cb23c3aab88884d6e9b215fc0ba7 (diff)
downloadopencode-cdeb82e9ca0213960202447896493c8d18cb867b.tar.gz
opencode-cdeb82e9ca0213960202447896493c8d18cb867b.zip
wip: desktop work
-rw-r--r--packages/desktop/src/pages/index.tsx134
-rw-r--r--packages/ui/src/components/diff.tsx8
-rw-r--r--packages/ui/src/styles/utilities.css65
3 files changed, 112 insertions, 95 deletions
diff --git a/packages/desktop/src/pages/index.tsx b/packages/desktop/src/pages/index.tsx
index ac6b6f9c8..341da2d95 100644
--- a/packages/desktop/src/pages/index.tsx
+++ b/packages/desktop/src/pages/index.tsx
@@ -670,98 +670,6 @@ export default function Page() {
<Message message={message} parts={parts()} />
</div>
</Show>
- {/* Response */}
- <div class="w-full flex flex-col gap-2">
- <Collapsible variant="ghost" open={expanded()} onOpenChange={setExpanded}>
- <Collapsible.Trigger class="text-text-weak hover:text-text-strong">
- <div class="flex items-center gap-1 self-stretch">
- <h2 class="text-12-medium">
- <Switch>
- <Match when={expanded()}>Hide steps</Match>
- <Match when={!expanded()}>Show steps</Match>
- </Switch>
- </h2>
- <Collapsible.Arrow />
- </div>
- </Collapsible.Trigger>
- <Collapsible.Content>
- <div class="w-full flex flex-col items-start self-stretch gap-8">
- <For each={assistantMessages()}>
- {(assistantMessage) => {
- const parts = createMemo(() => sync.data.part[assistantMessage.id])
- return <Message message={assistantMessage} parts={parts()} />
- }}
- </For>
- </div>
- </Collapsible.Content>
- </Collapsible>
- <Show when={working() && !expanded()}>
- {(_) => {
- const lastMessageWithText = createMemo(() =>
- assistantMessages().findLast((m) => {
- const parts = sync.data.part[m.id]
- return parts?.find((p) => p.type === "text")
- }),
- )
- const lastMessageWithReasoning = createMemo(() =>
- assistantMessages().findLast((m) => {
- const parts = sync.data.part[m.id]
- return parts?.find((p) => p.type === "reasoning")
- }),
- )
- const lastMessageWithTool = createMemo(() =>
- assistantMessages().findLast((m) => {
- const parts = sync.data.part[m.id]
- return parts?.find(
- (p) => p.type === "tool" && p.state.status === "completed",
- )
- }),
- )
- return (
- <div class="w-full flex flex-col gap-2">
- <Switch>
- <Match when={lastMessageWithText()}>
- {(last) => {
- const lastTextPart = createMemo(() =>
- sync.data.part[last().id].findLast((p) => p.type === "text"),
- )
- return (
- <Part message={last()} part={lastTextPart()!} hideDetails />
- )
- }}
- </Match>
- <Match when={lastMessageWithReasoning()}>
- {(last) => {
- const lastReasoningPart = createMemo(() =>
- sync.data.part[last().id].findLast(
- (p) => p.type === "reasoning",
- ),
- )
- return (
- <Part
- message={last()}
- part={lastReasoningPart()!}
- hideDetails
- />
- )
- }}
- </Match>
- </Switch>
- <Show when={lastMessageWithTool()}>
- {(last) => {
- const lastToolPart = createMemo(() =>
- sync.data.part[last().id].findLast(
- (p) => p.type === "tool" && p.state.status === "completed",
- ),
- )
- return <Part message={last()} part={lastToolPart()!} hideDetails />
- }}
- </Show>
- </div>
- )
- }}
- </Show>
- </div>
{/* Summary */}
<Show when={!working()}>
<div class="w-full flex flex-col gap-6 items-start self-stretch">
@@ -817,6 +725,48 @@ export default function Page() {
</Accordion>
</div>
</Show>
+ {/* Response */}
+ <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>
+ </Match>
+ <Match when={!working()}>
+ <Collapsible variant="ghost" open={expanded()} onOpenChange={setExpanded}>
+ <Collapsible.Trigger class="text-text-weak hover:text-text-strong">
+ <div class="flex items-center gap-1 self-stretch">
+ <div class="text-12-medium">
+ <Switch>
+ <Match when={expanded()}>Hide steps</Match>
+ <Match when={!expanded()}>Show steps</Match>
+ </Switch>
+ </div>
+ <Collapsible.Arrow />
+ </div>
+ </Collapsible.Trigger>
+ <Collapsible.Content>
+ <div class="w-full flex flex-col-reverse items-start self-stretch gap-8">
+ <For each={assistantMessages()}>
+ {(assistantMessage) => {
+ const parts = createMemo(
+ () => sync.data.part[assistantMessage.id],
+ )
+ return <Message message={assistantMessage} parts={parts()} />
+ }}
+ </For>
+ </div>
+ </Collapsible.Content>
+ </Collapsible>
+ </Match>
+ </Switch>
+ </div>
</div>
)
}}
diff --git a/packages/ui/src/components/diff.tsx b/packages/ui/src/components/diff.tsx
index 3eeab6d6f..731b1bfe0 100644
--- a/packages/ui/src/components/diff.tsx
+++ b/packages/ui/src/components/diff.tsx
@@ -54,18 +54,20 @@ export function Diff<T>(props: DiffProps<T>) {
// When ready to render, simply call .render with old/new file, optional
// annotations and a container element to hold the diff
createEffect(() => {
+ // @ts-expect-error
const instance = new FileDiff<T>({
- theme: "pierre-light",
+ // theme: "pierre-light",
+ // theme: "pierre-light",
// Or can also provide a 'themes' prop, which allows the code to adapt
// to your OS light or dark theme
- // themes: { dark: 'pierre-night', light: 'pierre-light' },
+ themes: { dark: "pierre-dark", light: "pierre-light" },
// When using the 'themes' prop, 'themeType' allows you to force 'dark'
// or 'light' theme, or inherit from the OS ('system') theme.
themeType: "system",
// Disable the line numbers for your diffs, generally not recommended
disableLineNumbers: false,
// Whether code should 'wrap' with long lines or 'scroll'.
- overflow: "scroll",
+ overflow: "wrap",
// Normally you shouldn't need this prop, but if you don't provide a
// valid filename or your file doesn't have an extension you may want to
// override the automatic detection. You can specify that language here:
diff --git a/packages/ui/src/styles/utilities.css b/packages/ui/src/styles/utilities.css
index 99b7760a0..9c6b73f9c 100644
--- a/packages/ui/src/styles/utilities.css
+++ b/packages/ui/src/styles/utilities.css
@@ -48,6 +48,71 @@
border-width: 0;
}
+.scroller {
+ /* --fade-height: 1.5rem; */
+ /**/
+ /* --mask-top: linear-gradient(to bottom, transparent, black var(--fade-height)); */
+ /* --mask-bottom: linear-gradient(to top, transparent, black var(--fade-height)); */
+ /**/
+ /* mask-image: var(--mask-top), var(--mask-bottom); */
+ /* mask-repeat: no-repeat; */
+ /* mask-size: 100% var(--fade-height); */
+
+ animation: scroll-fade linear;
+ animation-timeline: scroll(self);
+}
+
+/* Define the keyframes for the mask.
+ These percentages now map to scroll positions:
+ 0% = Scrolled to the top
+ 100% = Scrolled to the bottom
+*/
+@keyframes scroll-fade {
+ /* At the very top (0% scroll) */
+ 0% {
+ mask-image: linear-gradient(
+ to bottom,
+ black 90%,
+ /* Opaque, but start fade to bottom */ transparent 100%
+ );
+ }
+
+ /* A small amount scrolled (e.g., 5%)
+ This is where the top fade should be fully visible.
+ */
+ 5% {
+ mask-image: linear-gradient(
+ to bottom,
+ transparent 0%,
+ black 10%,
+ /* Fade-in top */ black 90%,
+ /* Fade-out bottom */ transparent 100%
+ );
+ }
+
+ /* Nearing the bottom (e.g., 95%)
+ The bottom fade should start disappearing.
+ */
+ 95% {
+ mask-image: linear-gradient(
+ to bottom,
+ transparent 0%,
+ black 10%,
+ /* Fade-in top */ black 90%,
+ /* Fade-out bottom */ transparent 100%
+ );
+ }
+
+ /* At the very bottom (100% scroll) */
+ 100% {
+ mask-image: linear-gradient(
+ to bottom,
+ transparent 0%,
+ black 10% /* Opaque, but start fade from top */
+ );
+ }
+}
+
.truncate-start {
text-overflow: ellipsis;
overflow: hidden;