Adam пре 3 месеци
родитељ
комит
ee7612a31c

+ 2 - 89
packages/desktop/src/pages/index.tsx

@@ -535,101 +535,14 @@ export default function Page() {
                             >
                             >
                               <For each={local.session.userMessages()}>
                               <For each={local.session.userMessages()}>
                                 {(message) => {
                                 {(message) => {
-                                  const countLines = (text: string) => {
-                                    if (!text) return 0
-                                    return text.split("\n").length
-                                  }
-
-                                  const additions = createMemo(
-                                    () =>
-                                      message.summary?.diffs.reduce((acc, diff) => acc + (diff.additions ?? 0), 0) ?? 0,
-                                  )
-
-                                  const deletions = createMemo(
-                                    () =>
-                                      message.summary?.diffs.reduce((acc, diff) => acc + (diff.deletions ?? 0), 0) ?? 0,
-                                  )
-
-                                  const totalBeforeLines = createMemo(
-                                    () =>
-                                      message.summary?.diffs.reduce((acc, diff) => acc + countLines(diff.before), 0) ??
-                                      0,
-                                  )
-
-                                  const blockCounts = createMemo(() => {
-                                    const TOTAL_BLOCKS = 5
-
-                                    const adds = additions()
-                                    const dels = deletions()
-                                    const unchanged = Math.max(0, totalBeforeLines() - dels)
-
-                                    const totalActivity = unchanged + adds + dels
-
-                                    if (totalActivity === 0) {
-                                      return { added: 0, deleted: 0, neutral: TOTAL_BLOCKS }
-                                    }
-
-                                    const percentAdded = adds / totalActivity
-                                    const percentDeleted = dels / totalActivity
-                                    const added_raw = percentAdded * TOTAL_BLOCKS
-                                    const deleted_raw = percentDeleted * TOTAL_BLOCKS
-
-                                    let added = adds > 0 ? Math.ceil(added_raw) : 0
-                                    let deleted = dels > 0 ? Math.ceil(deleted_raw) : 0
-
-                                    let total_allocated = added + deleted
-                                    if (total_allocated > TOTAL_BLOCKS) {
-                                      if (added_raw < deleted_raw) {
-                                        added = Math.floor(added_raw)
-                                      } else {
-                                        deleted = Math.floor(deleted_raw)
-                                      }
-
-                                      total_allocated = added + deleted
-                                      if (total_allocated > TOTAL_BLOCKS) {
-                                        if (added_raw < deleted_raw) {
-                                          deleted = Math.floor(deleted_raw)
-                                        } else {
-                                          added = Math.floor(added_raw)
-                                        }
-                                      }
-                                    }
-
-                                    const neutral = Math.max(0, TOTAL_BLOCKS - added - deleted)
-
-                                    return { added, deleted, neutral }
-                                  })
-
-                                  const ADD_COLOR = "var(--icon-diff-add-base)"
-                                  const DELETE_COLOR = "var(--icon-diff-delete-base)"
-                                  const NEUTRAL_COLOR = "var(--icon-weak-base)"
-
-                                  const visibleBlocks = createMemo(() => {
-                                    const counts = blockCounts()
-                                    const blocks = [
-                                      ...Array(counts.added).fill(ADD_COLOR),
-                                      ...Array(counts.deleted).fill(DELETE_COLOR),
-                                      ...Array(counts.neutral).fill(NEUTRAL_COLOR),
-                                    ]
-                                    return blocks.slice(0, 5)
-                                  })
+                                  const diffs = createMemo(() => message.summary?.diffs ?? [])
 
 
                                   return (
                                   return (
                                     <li
                                     <li
                                       class="group/li flex items-center gap-x-2 py-1 self-stretch cursor-default"
                                       class="group/li flex items-center gap-x-2 py-1 self-stretch cursor-default"
                                       onClick={() => local.session.setActiveMessage(message.id)}
                                       onClick={() => local.session.setActiveMessage(message.id)}
                                     >
                                     >
-                                      <div class="w-[18px] shrink-0">
-                                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 12" fill="none">
-                                          <g>
-                                            <For each={visibleBlocks()}>
-                                              {(color, i) => (
-                                                <rect x={i() * 4} width="2" height="12" rx="1" fill={color} />
-                                              )}
-                                            </For>
-                                          </g>
-                                        </svg>
-                                      </div>
+                                      <DiffChanges diff={diffs()} variant="bars" />
                                       <div
                                       <div
                                         data-active={local.session.activeMessage()?.id === message.id}
                                         data-active={local.session.activeMessage()?.id === message.id}
                                         classList={{
                                         classList={{

+ 11 - 0
packages/ui/src/components/diff-changes.css

@@ -26,3 +26,14 @@
     color: var(--text-diff-delete-base);
     color: var(--text-diff-delete-base);
   }
   }
 }
 }
+
+[data-component="diff-changes"][data-variant="bars"] {
+  width: 18px;
+  flex-shrink: 0;
+
+  svg {
+    display: block;
+    width: 100%;
+    height: auto;
+  }
+}

+ 104 - 6
packages/ui/src/components/diff-changes.tsx

@@ -1,7 +1,9 @@
 import type { FileDiff } from "@opencode-ai/sdk"
 import type { FileDiff } from "@opencode-ai/sdk"
-import { createMemo, Show } from "solid-js"
+import { createMemo, For, Match, Show, Switch } from "solid-js"
+
+export function DiffChanges(props: { diff: FileDiff | FileDiff[]; variant?: "default" | "bars" }) {
+  const variant = () => props.variant ?? "default"
 
 
-export function DiffChanges(props: { diff: FileDiff | FileDiff[] }) {
   const additions = createMemo(() =>
   const additions = createMemo(() =>
     Array.isArray(props.diff)
     Array.isArray(props.diff)
       ? props.diff.reduce((acc, diff) => acc + (diff.additions ?? 0), 0)
       ? props.diff.reduce((acc, diff) => acc + (diff.additions ?? 0), 0)
@@ -13,11 +15,107 @@ export function DiffChanges(props: { diff: FileDiff | FileDiff[] }) {
       : props.diff.deletions,
       : props.diff.deletions,
   )
   )
   const total = createMemo(() => (additions() ?? 0) + (deletions() ?? 0))
   const total = createMemo(() => (additions() ?? 0) + (deletions() ?? 0))
+
+  const countLines = (text: string) => {
+    if (!text) return 0
+    return text.split("\n").length
+  }
+
+  const totalBeforeLines = createMemo(() => {
+    if (!Array.isArray(props.diff)) return countLines(props.diff.before || "")
+    return props.diff.reduce((acc, diff) => acc + countLines(diff.before || ""), 0)
+  })
+
+  const blockCounts = createMemo(() => {
+    const TOTAL_BLOCKS = 5
+
+    const adds = additions() ?? 0
+    const dels = deletions() ?? 0
+
+    if (adds === 0 && dels === 0) {
+      return { added: 0, deleted: 0, neutral: TOTAL_BLOCKS }
+    }
+
+    const total = adds + dels
+
+    if (total < 5) {
+      const added = adds > 0 ? 1 : 0
+      const deleted = dels > 0 ? 1 : 0
+      const neutral = TOTAL_BLOCKS - added - deleted
+      return { added, deleted, neutral }
+    }
+
+    const ratio = adds > dels ? adds / dels : dels / adds
+    let BLOCKS_FOR_COLORS = TOTAL_BLOCKS
+
+    if (total < 20) {
+      BLOCKS_FOR_COLORS = TOTAL_BLOCKS - 1
+    } else if (ratio < 4) {
+      BLOCKS_FOR_COLORS = TOTAL_BLOCKS - 1
+    }
+
+    const percentAdded = adds / total
+    const percentDeleted = dels / total
+
+    const added_raw = percentAdded * BLOCKS_FOR_COLORS
+    const deleted_raw = percentDeleted * BLOCKS_FOR_COLORS
+
+    let added = adds > 0 ? Math.max(1, Math.round(added_raw)) : 0
+    let deleted = dels > 0 ? Math.max(1, Math.round(deleted_raw)) : 0
+
+    // Cap bars based on actual change magnitude
+    if (adds > 0 && adds <= 5) added = Math.min(added, 1)
+    if (adds > 5 && adds <= 10) added = Math.min(added, 2)
+    if (dels > 0 && dels <= 5) deleted = Math.min(deleted, 1)
+    if (dels > 5 && dels <= 10) deleted = Math.min(deleted, 2)
+
+    let total_allocated = added + deleted
+    if (total_allocated > BLOCKS_FOR_COLORS) {
+      if (added_raw > deleted_raw) {
+        added = BLOCKS_FOR_COLORS - deleted
+      } else {
+        deleted = BLOCKS_FOR_COLORS - added
+      }
+      total_allocated = added + deleted
+    }
+
+    const neutral = Math.max(0, TOTAL_BLOCKS - total_allocated)
+
+    return { added, deleted, neutral }
+  })
+
+  const ADD_COLOR = "var(--icon-diff-add-base)"
+  const DELETE_COLOR = "var(--icon-diff-delete-base)"
+  const NEUTRAL_COLOR = "var(--icon-weak-base)"
+
+  const visibleBlocks = createMemo(() => {
+    const counts = blockCounts()
+    const blocks = [
+      ...Array(counts.added).fill(ADD_COLOR),
+      ...Array(counts.deleted).fill(DELETE_COLOR),
+      ...Array(counts.neutral).fill(NEUTRAL_COLOR),
+    ]
+    return blocks.slice(0, 5)
+  })
+
   return (
   return (
-    <Show when={total() > 0}>
-      <div data-component="diff-changes">
-        <span data-slot="additions">{`+${additions()}`}</span>
-        <span data-slot="deletions">{`-${deletions()}`}</span>
+    <Show when={variant() === "default" ? total() > 0 : true}>
+      <div data-component="diff-changes" data-variant={variant()}>
+        <Switch>
+          <Match when={variant() === "bars"}>
+            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 12" fill="none">
+              <g>
+                <For each={visibleBlocks()}>
+                  {(color, i) => <rect x={i() * 4} width="2" height="12" rx="1" fill={color} />}
+                </For>
+              </g>
+            </svg>
+          </Match>
+          <Match when={variant() === "default"}>
+            <span data-slot="additions">{`+${additions()}`}</span>
+            <span data-slot="deletions">{`-${deletions()}`}</span>
+          </Match>
+        </Switch>
       </div>
       </div>
     </Show>
     </Show>
   )
   )