Skip to content

Commit 545f345

Browse files
committed
wip: desktop work
1 parent 77ae0b5 commit 545f345

File tree

7 files changed

+187
-107
lines changed

7 files changed

+187
-107
lines changed

packages/desktop/src/components/assistant-message.tsx

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,15 @@ import type { WriteTool } from "opencode/tool/write"
1717
import type { TodoWriteTool } from "opencode/tool/todo"
1818
import { DiffChanges } from "./diff-changes"
1919

20-
export function AssistantMessage(props: { message: AssistantMessage; parts: Part[] }) {
21-
const filteredParts = createMemo(() => props.parts?.filter((x) => x.type !== "tool" || x.tool !== "todoread"))
20+
export function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; lastToolOnly?: boolean }) {
21+
const filteredParts = createMemo(() => {
22+
let tool = false
23+
return props.parts?.filter((x) => {
24+
if (x.type === "tool" && props.lastToolOnly && tool) return false
25+
if (x.type === "tool") tool = true
26+
return x.type !== "tool" || x.tool !== "todoread"
27+
})
28+
})
2229
return (
2330
<div class="w-full flex flex-col items-start gap-4">
2431
<For each={filteredParts()}>
@@ -71,17 +78,14 @@ function ToolPart(props: { part: ToolPart; message: AssistantMessage }) {
7178
// const permission = permissions[permissionIndex]
7279

7380
return (
74-
<>
75-
<Dynamic
76-
component={render}
77-
input={input}
78-
tool={props.part.tool}
79-
metadata={metadata}
80-
// permission={permission?.metadata ?? {}}
81-
output={props.part.state.status === "completed" ? props.part.state.output : undefined}
82-
/>
83-
{/* <Show when={props.part.state.status === "error"}>{props.part.state.error.replace("Error: ", "")}</Show> */}
84-
</>
81+
<Dynamic
82+
component={render}
83+
input={input}
84+
tool={props.part.tool}
85+
metadata={metadata}
86+
// permission={permission?.metadata ?? {}}
87+
output={props.part.state.status === "completed" ? props.part.state.output : undefined}
88+
/>
8589
)
8690
})
8791

@@ -166,6 +170,9 @@ function BasicTool(props: { icon: IconProps["name"]; trigger: TriggerTitle | JSX
166170
<Collapsible.Content>{props.children}</Collapsible.Content>
167171
</Show>
168172
</Collapsible>
173+
// <>
174+
// <Show when={props.part.state.status === "error"}>{props.part.state.error.replace("Error: ", "")}</Show>
175+
// </>
169176
)
170177
}
171178

packages/desktop/src/components/prompt-input.tsx

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ import { DateTime } from "luxon"
1010

1111
interface PartBase {
1212
content: string
13+
start: number
14+
end: number
1315
}
1416

15-
interface TextPart extends PartBase {
17+
export interface TextPart extends PartBase {
1618
type: "text"
1719
}
1820

19-
interface FileAttachmentPart extends PartBase {
21+
export interface FileAttachmentPart extends PartBase {
2022
type: "file"
2123
path: string
2224
selection?: TextSelection
@@ -34,7 +36,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
3436
const local = useLocal()
3537
let editorRef!: HTMLDivElement
3638

37-
const defaultParts = [{ type: "text", content: "" } as const]
39+
const defaultParts = [{ type: "text", content: "", start: 0, end: 0 } as const]
3840
const [store, setStore] = createStore<{
3941
contentParts: ContentPart[]
4042
popoverIsOpen: boolean
@@ -51,7 +53,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
5153
event.stopPropagation()
5254
// @ts-expect-error
5355
const plainText = (event.clipboardData || window.clipboardData)?.getData("text/plain") ?? ""
54-
addPart({ type: "text", content: plainText })
56+
addPart({ type: "text", content: plainText, start: 0, end: 0 })
5557
}
5658

5759
onMount(() => {
@@ -74,7 +76,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
7476
key: (x) => x,
7577
onSelect: (path) => {
7678
if (!path) return
77-
addPart({ type: "file", path, content: "@" + getFilename(path) })
79+
addPart({ type: "file", path, content: "@" + getFilename(path), start: 0, end: 0 })
7880
setStore("popoverIsOpen", false)
7981
},
8082
})
@@ -117,17 +119,26 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
117119

118120
const parseFromDOM = (): ContentPart[] => {
119121
const newParts: ContentPart[] = []
122+
let position = 0
120123
editorRef.childNodes.forEach((node) => {
121124
if (node.nodeType === Node.TEXT_NODE) {
122-
if (node.textContent) newParts.push({ type: "text", content: node.textContent })
125+
if (node.textContent) {
126+
const content = node.textContent
127+
newParts.push({ type: "text", content, start: position, end: position + content.length })
128+
position += content.length
129+
}
123130
} else if (node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).dataset.type) {
124131
switch ((node as HTMLElement).dataset.type) {
125132
case "file":
133+
const content = node.textContent!
126134
newParts.push({
127135
type: "file",
128136
path: (node as HTMLElement).dataset.path!,
129-
content: node.textContent!,
137+
content,
138+
start: position,
139+
end: position + content.length,
130140
})
141+
position += content.length
131142
break
132143
default:
133144
break
@@ -163,17 +174,19 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
163174
const startIndex = atMatch ? atMatch.index! : cursorPosition
164175
const endIndex = atMatch ? cursorPosition : cursorPosition
165176

166-
const pushText = (acc: { parts: ContentPart[] }, value: string) => {
177+
const pushText = (acc: { parts: ContentPart[]; runningIndex: number }, value: string) => {
167178
if (!value) return
168179
const last = acc.parts[acc.parts.length - 1]
169180
if (last && last.type === "text") {
170181
acc.parts[acc.parts.length - 1] = {
171182
type: "text",
172183
content: last.content + value,
184+
start: last.start,
185+
end: last.end + value.length,
173186
}
174187
return
175188
}
176-
acc.parts.push({ type: "text", content: value })
189+
acc.parts.push({ type: "text", content: value, start: acc.runningIndex, end: acc.runningIndex + value.length })
177190
}
178191

179192
const {
@@ -183,20 +196,20 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
183196
} = store.contentParts.reduce(
184197
(acc, item) => {
185198
if (acc.inserted) {
186-
acc.parts.push(item)
199+
acc.parts.push({ ...item, start: acc.runningIndex, end: acc.runningIndex + item.content.length })
187200
acc.runningIndex += item.content.length
188201
return acc
189202
}
190203

191204
const nextIndex = acc.runningIndex + item.content.length
192205
if (nextIndex <= startIndex) {
193-
acc.parts.push(item)
206+
acc.parts.push({ ...item, start: acc.runningIndex, end: acc.runningIndex + item.content.length })
194207
acc.runningIndex = nextIndex
195208
return acc
196209
}
197210

198211
if (item.type !== "text") {
199-
acc.parts.push(item)
212+
acc.parts.push({ ...item, start: acc.runningIndex, end: acc.runningIndex + item.content.length })
200213
acc.runningIndex = nextIndex
201214
return acc
202215
}
@@ -207,24 +220,27 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
207220
const tail = item.content.slice(tailLength)
208221

209222
pushText(acc, head)
223+
acc.runningIndex += head.length
210224

211225
if (part.type === "text") {
212226
pushText(acc, part.content)
227+
acc.runningIndex += part.content.length
213228
}
214229
if (part.type !== "text") {
215-
acc.parts.push({ ...part })
230+
acc.parts.push({ ...part, start: acc.runningIndex, end: acc.runningIndex + part.content.length })
231+
acc.runningIndex += part.content.length
216232
}
217233

218234
const needsGap = Boolean(atMatch)
219235
const rest = needsGap ? (tail ? (/^\s/.test(tail) ? tail : ` ${tail}`) : " ") : tail
220236
pushText(acc, rest)
237+
acc.runningIndex += rest.length
221238

222239
const baseCursor = startIndex + part.content.length
223240
const cursorAddition = needsGap && rest.length > 0 ? 1 : 0
224241
acc.cursorPositionAfter = baseCursor + cursorAddition
225242

226243
acc.inserted = true
227-
acc.runningIndex = nextIndex
228244
return acc
229245
},
230246
{
@@ -237,9 +253,18 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
237253

238254
if (!inserted) {
239255
const baseParts = store.contentParts.filter((item) => !(item.type === "text" && item.content === ""))
240-
const appendedAcc = { parts: [...baseParts] as ContentPart[] }
241-
if (part.type === "text") pushText(appendedAcc, part.content)
242-
if (part.type !== "text") appendedAcc.parts.push({ ...part })
256+
const runningIndex = baseParts.reduce((sum, p) => sum + p.content.length, 0)
257+
const appendedAcc = { parts: [...baseParts] as ContentPart[], runningIndex }
258+
if (part.type === "text") {
259+
pushText(appendedAcc, part.content)
260+
}
261+
if (part.type !== "text") {
262+
appendedAcc.parts.push({
263+
...part,
264+
start: appendedAcc.runningIndex,
265+
end: appendedAcc.runningIndex + part.content.length,
266+
})
267+
}
243268
const next = appendedAcc.parts.length > 0 ? appendedAcc.parts : defaultParts
244269
setStore("contentParts", next)
245270
setStore("popoverIsOpen", false)

packages/desktop/src/context/local.tsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -429,13 +429,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
429429
.sort((a, b) => b.id.localeCompare(a.id)),
430430
)
431431

432-
const working = createMemo(() => {
433-
const last = messages()[messages().length - 1]
434-
if (!last) return false
435-
if (last.role === "user") return true
436-
return !last.time.completed
437-
})
438-
439432
const cost = createMemo(() => {
440433
const total = pipe(
441434
messages(),
@@ -487,6 +480,9 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
487480
const getMessageText = (message: Message | Message[] | undefined): string => {
488481
if (!message) return ""
489482
if (Array.isArray(message)) return message.map((m) => getMessageText(m)).join(" ")
483+
const fileParts = sync.data.part[message.id]?.filter((p) => p.type === "file")
484+
console.log(fileParts)
485+
490486
return sync.data.part[message.id]
491487
?.filter((p) => p.type === "text")
492488
?.filter((p) => !p.synthetic)
@@ -506,7 +502,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
506502
messages,
507503
messagesWithValidParts,
508504
userMessages,
509-
working,
505+
// working,
510506
getMessageText,
511507
setActive(sessionId: string | undefined) {
512508
setStore("active", sessionId)

0 commit comments

Comments
 (0)