Skip to content

Commit fe8f6d7

Browse files
committed
wip: desktop work
1 parent 59b5f53 commit fe8f6d7

File tree

8 files changed

+350
-72
lines changed

8 files changed

+350
-72
lines changed
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { type FileContents, FileDiff, type DiffLineAnnotation } from "@pierre/precision-diffs"
2+
3+
export interface DiffProps {
4+
before: FileContents
5+
after: FileContents
6+
}
7+
8+
export function Diff(props: DiffProps) {
9+
let container!: HTMLDivElement
10+
11+
console.log(props)
12+
13+
interface ThreadMetadata {
14+
threadId: string
15+
}
16+
17+
const lineAnnotations: DiffLineAnnotation<ThreadMetadata>[] = [
18+
{
19+
side: "additions",
20+
// The line number specified for an annotation is the visual line number
21+
// you see in the number column of a diff
22+
lineNumber: 16,
23+
metadata: { threadId: "68b329da9893e34099c7d8ad5cb9c940" },
24+
},
25+
]
26+
27+
const instance = new FileDiff<ThreadMetadata>({
28+
// You can provide a 'theme' prop that maps to any
29+
// built in shiki theme or you can register a custom
30+
// theme. We also include 2 custom themes
31+
//
32+
// 'pierre-night' and 'pierre-light
33+
//
34+
// For the rest of the available shiki themes, check out:
35+
// https://shiki.style/themes
36+
theme: "none",
37+
// Or can also provide a 'themes' prop, which allows the code to adapt
38+
// to your OS light or dark theme
39+
// themes: { dark: 'pierre-night', light: 'pierre-light' },
40+
// When using the 'themes' prop, 'themeType' allows you to force 'dark'
41+
// or 'light' theme, or inherit from the OS ('system') theme.
42+
themeType: "system",
43+
// Disable the line numbers for your diffs, generally not recommended
44+
disableLineNumbers: false,
45+
// Whether code should 'wrap' with long lines or 'scroll'.
46+
overflow: "scroll",
47+
// Normally you shouldn't need this prop, but if you don't provide a
48+
// valid filename or your file doesn't have an extension you may want to
49+
// override the automatic detection. You can specify that language here:
50+
// https://shiki.style/languages
51+
// lang?: SupportedLanguages;
52+
// 'diffStyle' controls whether the diff is presented side by side or
53+
// in a unified (single column) view
54+
diffStyle: "split",
55+
// Line decorators to help highlight changes.
56+
// 'bars' (default):
57+
// Shows some red-ish or green-ish (theme dependent) bars on the left
58+
// edge of relevant lines
59+
//
60+
// 'classic':
61+
// shows '+' characters on additions and '-' characters on deletions
62+
//
63+
// 'none':
64+
// No special diff indicators are shown
65+
diffIndicators: "bars",
66+
// By default green-ish or red-ish background are shown on added and
67+
// deleted lines respectively. Disable that feature here
68+
disableBackground: false,
69+
// Diffs are split up into hunks, this setting customizes what to show
70+
// between each hunk.
71+
//
72+
// 'line-info' (default):
73+
// Shows a bar that tells you how many lines are collapsed. If you are
74+
// using the oldFile/newFile API then you can click those bars to
75+
// expand the content between them
76+
//
77+
// 'metadata':
78+
// Shows the content you'd see in a normal patch file, usually in some
79+
// format like '@@ -60,6 +60,22 @@'. You cannot use these to expand
80+
// hidden content
81+
//
82+
// 'simple':
83+
// Just a subtle bar separator between each hunk
84+
hunkSeparators: "line-info",
85+
// On lines that have both additions and deletions, we can run a
86+
// separate diff check to mark parts of the lines that change.
87+
// 'none':
88+
// Do not show these secondary highlights
89+
//
90+
// 'char':
91+
// Show changes at a per character granularity
92+
//
93+
// 'word':
94+
// Show changes but rounded up to word boundaries
95+
//
96+
// 'word-alt' (default):
97+
// Similar to 'word', however we attempt to minimize single character
98+
// gaps between highlighted changes
99+
lineDiffType: "word-alt",
100+
// If lines exceed these character lengths then we won't perform the
101+
// line lineDiffType check
102+
maxLineDiffLength: 1000,
103+
// If any line in the diff exceeds this value then we won't attempt to
104+
// syntax highlight the diff
105+
maxLineLengthForHighlighting: 1000,
106+
// Enabling this property will hide the file header with file name and
107+
// diff stats.
108+
disableFileHeader: false,
109+
// You can optionally pass a render function for rendering out line
110+
// annotations. Just return the dom node to render
111+
renderAnnotation(annotation: DiffLineAnnotation<ThreadMetadata>): HTMLElement {
112+
// Despite the diff itself being rendered in the shadow dom,
113+
// annotations are inserted via the web components 'slots' api and you
114+
// can use all your normal normal css and styling for them
115+
const element = document.createElement("div")
116+
element.innerText = annotation.metadata.threadId
117+
return element
118+
},
119+
})
120+
121+
// If you ever want to update the options for an instance, simple call
122+
// 'setOptions' with the new options. Bear in mind, this does NOT merge
123+
// existing properties, it's a full replace
124+
instance.setOptions({
125+
...instance.options,
126+
theme: "pierre-dark",
127+
themes: undefined,
128+
})
129+
130+
// When ready to render, simply call .render with old/new file, optional
131+
// annotations and a container element to hold the diff
132+
instance.render({
133+
oldFile: props.before,
134+
newFile: props.after,
135+
lineAnnotations,
136+
containerWrapper: container,
137+
})
138+
139+
return <div ref={container} />
140+
}

packages/desktop/src/context/local.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -467,11 +467,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
467467
)
468468
})
469469

470-
const activeAssistantMessagesWithText = createMemo(() => {
471-
if (!store.active || !activeAssistantMessages()) return []
472-
return activeAssistantMessages()?.filter((m) => sync.data.part[m.id].find((p) => p.type === "text"))
473-
})
474-
475470
const model = createMemo(() => {
476471
if (!last()) return
477472
const model = sync.data.provider.find((x) => x.id === last().providerID)?.models[last().modelID]
@@ -510,7 +505,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
510505
active,
511506
activeMessage,
512507
activeAssistantMessages,
513-
activeAssistantMessagesWithText,
514508
lastUserMessage,
515509
cost,
516510
last,

packages/desktop/src/pages/index.tsx

Lines changed: 91 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Button, List, SelectDialog, Tooltip, IconButton, Tabs, Icon } from "@opencode-ai/ui"
22
import { FileIcon } from "@/ui"
33
import FileTree from "@/components/file-tree"
4-
import { For, onCleanup, onMount, Show, Match, Switch, createSignal, createEffect } from "solid-js"
4+
import { For, onCleanup, onMount, Show, Match, Switch, createSignal, createEffect, createMemo } from "solid-js"
55
import { useLocal, type LocalFile, type TextSelection } from "@/context/local"
66
import { createStore } from "solid-js/store"
77
import { getDirectory, getFilename } from "@/utils"
@@ -21,6 +21,7 @@ import type { JSX } from "solid-js"
2121
import { Code } from "@/components/code"
2222
import { useSync } from "@/context/sync"
2323
import { useSDK } from "@/context/sdk"
24+
import { Diff } from "@/components/diff"
2425

2526
export default function Page() {
2627
const local = useLocal()
@@ -374,27 +375,36 @@ export default function Page() {
374375
onSelect={(s) => local.session.setActive(s?.id)}
375376
onHover={(s) => (!!s ? sync.session.sync(s?.id) : undefined)}
376377
>
377-
{(session) => (
378-
<Tooltip placement="right" value={session.title}>
379-
<div>
380-
<div class="flex items-center self-stretch gap-6">
381-
<span class="text-14-regular text-text-strong overflow-hidden text-ellipsis truncate">
382-
{session.title}
383-
</span>
384-
<span class="text-12-regular text-text-weak text-right whitespace-nowrap">
385-
{DateTime.fromMillis(session.time.updated).toRelative()}
386-
</span>
387-
</div>
388-
<div class="flex justify-between items-center self-stretch">
389-
<span class="text-12-regular text-text-weak">2 files changed</span>
390-
<div class="flex gap-2 justify-end items-center">
391-
<span class="text-12-mono text-right text-text-diff-add-base">+43</span>
392-
<span class="text-12-mono text-right text-text-diff-delete-base">-2</span>
378+
{(session) => {
379+
const diffs = createMemo(() => session.summary?.diffs ?? [])
380+
const filesChanged = createMemo(() => diffs().length)
381+
const additions = createMemo(() => diffs().reduce((acc, diff) => (acc ?? 0) + (diff.additions ?? 0), 0))
382+
const deletions = createMemo(() => diffs().reduce((acc, diff) => (acc ?? 0) + (diff.deletions ?? 0), 0))
383+
384+
return (
385+
<Tooltip placement="right" value={session.title}>
386+
<div>
387+
<div class="flex items-center self-stretch gap-6">
388+
<span class="text-14-regular text-text-strong overflow-hidden text-ellipsis truncate">
389+
{session.title}
390+
</span>
391+
<span class="text-12-regular text-text-weak text-right whitespace-nowrap">
392+
{DateTime.fromMillis(session.time.updated).toRelative()}
393+
</span>
394+
</div>
395+
<div class="flex justify-between items-center self-stretch">
396+
<span class="text-12-regular text-text-weak">{`${filesChanged() || "No"} file${filesChanged() !== 1 ? "s" : ""} changed`}</span>
397+
<Show when={additions() || deletions()}>
398+
<div class="flex gap-2 justify-end items-center">
399+
<span class="text-12-mono text-right text-text-diff-add-base">{`+${additions()}`}</span>
400+
<span class="text-12-mono text-right text-text-diff-delete-base">{`-${deletions()}`}</span>
401+
</div>
402+
</Show>
393403
</div>
394404
</div>
395-
</div>
396-
</Tooltip>
397-
)}
405+
</Tooltip>
406+
)
407+
}}
398408
</List>
399409
</div>
400410
</div>
@@ -521,60 +531,77 @@ export default function Page() {
521531
{(activeSession) => (
522532
<div class="py-3 flex flex-col flex-1 min-h-0">
523533
<div class="flex items-start gap-8 flex-1 min-h-0">
524-
<ul role="list" class="w-60 shrink-0 flex flex-col items-start gap-1">
525-
<For each={local.session.userMessages()}>
526-
{(message) => (
527-
<li
528-
class="group/li flex items-center gap-x-2 py-1 self-stretch cursor-default"
529-
onClick={() => local.session.setActiveMessage(message.id)}
530-
>
531-
<div class="w-[18px] shrink-0">
532-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 12" fill="none">
533-
<g>
534-
<rect x="0" width="2" height="12" rx="1" fill="#CFCECD" />
535-
<rect x="4" width="2" height="12" rx="1" fill="#CFCECD" />
536-
<rect x="8" width="2" height="12" rx="1" fill="#CFCECD" />
537-
<rect x="12" width="2" height="12" rx="1" fill="#CFCECD" />
538-
<rect x="16" width="2" height="12" rx="1" fill="#CFCECD" />
539-
</g>
540-
</svg>
541-
</div>
542-
<div
543-
data-active={local.session.activeMessage()?.id === message.id}
544-
classList={{
545-
"text-14-regular text-text-weak whitespace-nowrap truncate min-w-0": true,
546-
"text-text-weak data-[active=true]:text-text-strong group-hover/li:text-text-base": true,
547-
}}
534+
<Show when={local.session.userMessages().length > 1}>
535+
<ul role="list" class="w-60 shrink-0 flex flex-col items-start gap-1">
536+
<For each={local.session.userMessages()}>
537+
{(message) => (
538+
<li
539+
class="group/li flex items-center gap-x-2 py-1 self-stretch cursor-default"
540+
onClick={() => local.session.setActiveMessage(message.id)}
548541
>
549-
{local.session.getMessageText(message)}
550-
</div>
551-
</li>
552-
)}
553-
</For>
554-
</ul>
542+
<div class="w-[18px] shrink-0">
543+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 12" fill="none">
544+
<g>
545+
<rect x="0" width="2" height="12" rx="1" fill="#CFCECD" />
546+
<rect x="4" width="2" height="12" rx="1" fill="#CFCECD" />
547+
<rect x="8" width="2" height="12" rx="1" fill="#CFCECD" />
548+
<rect x="12" width="2" height="12" rx="1" fill="#CFCECD" />
549+
<rect x="16" width="2" height="12" rx="1" fill="#CFCECD" />
550+
</g>
551+
</svg>
552+
</div>
553+
<div
554+
data-active={local.session.activeMessage()?.id === message.id}
555+
classList={{
556+
"text-14-regular text-text-weak whitespace-nowrap truncate min-w-0": true,
557+
"text-text-weak data-[active=true]:text-text-strong group-hover/li:text-text-base": true,
558+
}}
559+
>
560+
{local.session.getMessageText(message)}
561+
</div>
562+
</li>
563+
)}
564+
</For>
565+
</ul>
566+
</Show>
555567
<div
556568
ref={messageScrollElement}
557569
class="grow min-w-0 h-full overflow-y-auto no-scrollbar snap-y"
558570
>
559571
<div class="flex flex-col items-start gap-50 pb-[800px]">
560572
<For each={local.session.userMessages()}>
561-
{(message) => (
562-
<div
563-
data-message={message.id}
564-
class="flex flex-col items-start self-stretch gap-8 pt-1.5 snap-start"
565-
>
566-
<div class="flex flex-col items-start gap-4">
567-
<div class="text-14-medium text-text-strong overflow-hidden text-ellipsis min-w-0">
568-
{local.session.getMessageText(message)}
573+
{(message) => {
574+
console.log(message)
575+
return (
576+
<div
577+
data-message={message.id}
578+
class="flex flex-col items-start self-stretch gap-8 pt-1.5 snap-start"
579+
>
580+
<div class="flex flex-col items-start gap-4">
581+
<div class="text-14-medium text-text-strong overflow-hidden text-ellipsis min-w-0">
582+
{local.session.getMessageText(message)}
583+
</div>
584+
<div class="text-14-regular text-text-base">{message.summary?.text}</div>
569585
</div>
570-
<div class="text-14-regular text-text-base">
571-
{message.summary?.text ||
572-
local.session.getMessageText(local.session.activeAssistantMessagesWithText())}
586+
<div class="">
587+
<For each={message.summary?.diffs}>
588+
{(diff) => (
589+
<Diff
590+
before={{
591+
name: diff.file!,
592+
contents: diff.before!,
593+
}}
594+
after={{
595+
name: diff.file!,
596+
contents: diff.after!,
597+
}}
598+
/>
599+
)}
600+
</For>
573601
</div>
574602
</div>
575-
<div class=""></div>
576-
</div>
577-
)}
603+
)
604+
}}
578605
</For>
579606
</div>
580607
</div>

packages/opencode/test/fixture/fixture.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { $ } from "bun"
2+
import { realpathSync } from "fs"
23
import os from "os"
34
import path from "path"
45

@@ -17,7 +18,7 @@ export async function tmpdir<T>(options?: TmpDirOptions<T>) {
1718
await options?.dispose?.(dirpath)
1819
await $`rm -rf ${dirpath}`.quiet()
1920
},
20-
path: dirpath,
21+
path: realpathSync(dirpath),
2122
extra: extra as T,
2223
}
2324
return result

0 commit comments

Comments
 (0)