Adam 3 месяцев назад
Родитель
Сommit
cdeb82e9ca
3 измененных файлов с 112 добавлено и 95 удалено
  1. 42 92
      packages/desktop/src/pages/index.tsx
  2. 5 3
      packages/ui/src/components/diff.tsx
  3. 65 0
      packages/ui/src/styles/utilities.css

+ 42 - 92
packages/desktop/src/pages/index.tsx

@@ -670,98 +670,6 @@ export default function Page() {
                                           <Message message={message} parts={parts()} />
                                           <Message message={message} parts={parts()} />
                                         </div>
                                         </div>
                                       </Show>
                                       </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 */}
                                       {/* Summary */}
                                       <Show when={!working()}>
                                       <Show when={!working()}>
                                         <div class="w-full flex flex-col gap-6 items-start self-stretch">
                                         <div class="w-full flex flex-col gap-6 items-start self-stretch">
@@ -817,6 +725,48 @@ export default function Page() {
                                           </Accordion>
                                           </Accordion>
                                         </div>
                                         </div>
                                       </Show>
                                       </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>
                                     </div>
                                   )
                                   )
                                 }}
                                 }}

+ 5 - 3
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
   // When ready to render, simply call .render with old/new file, optional
   // annotations and a container element to hold the diff
   // annotations and a container element to hold the diff
   createEffect(() => {
   createEffect(() => {
+    // @ts-expect-error
     const instance = new FileDiff<T>({
     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
       // Or can also provide a 'themes' prop, which allows the code to adapt
       // to your OS light or dark theme
       // 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'
       // When using the 'themes' prop, 'themeType' allows you to force 'dark'
       // or 'light' theme, or inherit from the OS ('system') theme.
       // or 'light' theme, or inherit from the OS ('system') theme.
       themeType: "system",
       themeType: "system",
       // Disable the line numbers for your diffs, generally not recommended
       // Disable the line numbers for your diffs, generally not recommended
       disableLineNumbers: false,
       disableLineNumbers: false,
       // Whether code should 'wrap' with long lines or 'scroll'.
       // 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
       // 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
       // valid filename or your file doesn't have an extension you may want to
       // override the automatic detection. You can specify that language here:
       // override the automatic detection. You can specify that language here:

+ 65 - 0
packages/ui/src/styles/utilities.css

@@ -48,6 +48,71 @@
   border-width: 0;
   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 {
 .truncate-start {
   text-overflow: ellipsis;
   text-overflow: ellipsis;
   overflow: hidden;
   overflow: hidden;