|
@@ -1,4 +1,11 @@
|
|
|
-import { AssistantMessage, Part as PartType, TextPart, ToolPart } from "@opencode-ai/sdk/v2/client"
|
|
|
|
|
|
|
+import {
|
|
|
|
|
+ AssistantMessage,
|
|
|
|
|
+ Message as MessageType,
|
|
|
|
|
+ Part as PartType,
|
|
|
|
|
+ type Permission,
|
|
|
|
|
+ TextPart,
|
|
|
|
|
+ ToolPart,
|
|
|
|
|
+} from "@opencode-ai/sdk/v2/client"
|
|
|
import { useData } from "../context"
|
|
import { useData } from "../context"
|
|
|
import { useDiffComponent } from "../context/diff"
|
|
import { useDiffComponent } from "../context/diff"
|
|
|
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
|
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
|
@@ -61,13 +68,20 @@ function computeStatusFromPart(part: PartType | undefined): string | undefined {
|
|
|
return undefined
|
|
return undefined
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function same<T>(a: readonly T[], b: readonly T[]) {
|
|
|
|
|
+ if (a === b) return true
|
|
|
|
|
+ if (a.length !== b.length) return false
|
|
|
|
|
+ return a.every((x, i) => x === b[i])
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function AssistantMessageItem(props: {
|
|
function AssistantMessageItem(props: {
|
|
|
message: AssistantMessage
|
|
message: AssistantMessage
|
|
|
responsePartId: string | undefined
|
|
responsePartId: string | undefined
|
|
|
hideResponsePart: boolean
|
|
hideResponsePart: boolean
|
|
|
}) {
|
|
}) {
|
|
|
const data = useData()
|
|
const data = useData()
|
|
|
- const msgParts = createMemo(() => data.store.part[props.message.id] ?? [])
|
|
|
|
|
|
|
+ const emptyParts: PartType[] = []
|
|
|
|
|
+ const msgParts = createMemo(() => data.store.part[props.message.id] ?? emptyParts)
|
|
|
const lastTextPart = createMemo(() => {
|
|
const lastTextPart = createMemo(() => {
|
|
|
const parts = msgParts()
|
|
const parts = msgParts()
|
|
|
for (let i = parts.length - 1; i >= 0; i--) {
|
|
for (let i = parts.length - 1; i >= 0; i--) {
|
|
@@ -109,7 +123,14 @@ export function SessionTurn(
|
|
|
const data = useData()
|
|
const data = useData()
|
|
|
const diffComponent = useDiffComponent()
|
|
const diffComponent = useDiffComponent()
|
|
|
|
|
|
|
|
- const allMessages = createMemo(() => data.store.message[props.sessionID] ?? [])
|
|
|
|
|
|
|
+ const emptyMessages: MessageType[] = []
|
|
|
|
|
+ const emptyParts: PartType[] = []
|
|
|
|
|
+ const emptyAssistant: AssistantMessage[] = []
|
|
|
|
|
+ const emptyPermissions: Permission[] = []
|
|
|
|
|
+ const emptyPermissionParts: { part: ToolPart; message: AssistantMessage }[] = []
|
|
|
|
|
+ const idle = { type: "idle" as const }
|
|
|
|
|
+
|
|
|
|
|
+ const allMessages = createMemo(() => data.store.message[props.sessionID] ?? emptyMessages)
|
|
|
|
|
|
|
|
const messageIndex = createMemo(() => {
|
|
const messageIndex = createMemo(() => {
|
|
|
const messages = allMessages()
|
|
const messages = allMessages()
|
|
@@ -147,27 +168,31 @@ export function SessionTurn(
|
|
|
|
|
|
|
|
const parts = createMemo(() => {
|
|
const parts = createMemo(() => {
|
|
|
const msg = message()
|
|
const msg = message()
|
|
|
- if (!msg) return []
|
|
|
|
|
- return data.store.part[msg.id] ?? []
|
|
|
|
|
|
|
+ if (!msg) return emptyParts
|
|
|
|
|
+ return data.store.part[msg.id] ?? emptyParts
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- const assistantMessages = createMemo(() => {
|
|
|
|
|
- const msg = message()
|
|
|
|
|
- if (!msg) return [] as AssistantMessage[]
|
|
|
|
|
-
|
|
|
|
|
- const messages = allMessages()
|
|
|
|
|
- const index = messageIndex()
|
|
|
|
|
- if (index < 0) return [] as AssistantMessage[]
|
|
|
|
|
-
|
|
|
|
|
- const result: AssistantMessage[] = []
|
|
|
|
|
- for (let i = index + 1; i < messages.length; i++) {
|
|
|
|
|
- const item = messages[i]
|
|
|
|
|
- if (!item) continue
|
|
|
|
|
- if (item.role === "user") break
|
|
|
|
|
- if (item.role === "assistant" && item.parentID === msg.id) result.push(item as AssistantMessage)
|
|
|
|
|
- }
|
|
|
|
|
- return result
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ const assistantMessages = createMemo(
|
|
|
|
|
+ () => {
|
|
|
|
|
+ const msg = message()
|
|
|
|
|
+ if (!msg) return emptyAssistant
|
|
|
|
|
+
|
|
|
|
|
+ const messages = allMessages()
|
|
|
|
|
+ const index = messageIndex()
|
|
|
|
|
+ if (index < 0) return emptyAssistant
|
|
|
|
|
+
|
|
|
|
|
+ const result: AssistantMessage[] = []
|
|
|
|
|
+ for (let i = index + 1; i < messages.length; i++) {
|
|
|
|
|
+ const item = messages[i]
|
|
|
|
|
+ if (!item) continue
|
|
|
|
|
+ if (item.role === "user") break
|
|
|
|
|
+ if (item.role === "assistant" && item.parentID === msg.id) result.push(item as AssistantMessage)
|
|
|
|
|
+ }
|
|
|
|
|
+ return result
|
|
|
|
|
+ },
|
|
|
|
|
+ emptyAssistant,
|
|
|
|
|
+ { equals: same },
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
const lastAssistantMessage = createMemo(() => assistantMessages().at(-1))
|
|
const lastAssistantMessage = createMemo(() => assistantMessages().at(-1))
|
|
|
|
|
|
|
@@ -176,7 +201,7 @@ export function SessionTurn(
|
|
|
const lastTextPart = createMemo(() => {
|
|
const lastTextPart = createMemo(() => {
|
|
|
const msgs = assistantMessages()
|
|
const msgs = assistantMessages()
|
|
|
for (let mi = msgs.length - 1; mi >= 0; mi--) {
|
|
for (let mi = msgs.length - 1; mi >= 0; mi--) {
|
|
|
- const msgParts = data.store.part[msgs[mi].id] ?? []
|
|
|
|
|
|
|
+ const msgParts = data.store.part[msgs[mi].id] ?? emptyParts
|
|
|
for (let pi = msgParts.length - 1; pi >= 0; pi--) {
|
|
for (let pi = msgParts.length - 1; pi >= 0; pi--) {
|
|
|
const part = msgParts[pi]
|
|
const part = msgParts[pi]
|
|
|
if (part?.type === "text") return part as TextPart
|
|
if (part?.type === "text") return part as TextPart
|
|
@@ -196,25 +221,25 @@ export function SessionTurn(
|
|
|
return false
|
|
return false
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- const permissions = createMemo(() => data.store.permission?.[props.sessionID] ?? [])
|
|
|
|
|
|
|
+ const permissions = createMemo(() => data.store.permission?.[props.sessionID] ?? emptyPermissions)
|
|
|
const permissionCount = createMemo(() => permissions().length)
|
|
const permissionCount = createMemo(() => permissions().length)
|
|
|
const nextPermission = createMemo(() => permissions()[0])
|
|
const nextPermission = createMemo(() => permissions()[0])
|
|
|
|
|
|
|
|
const permissionParts = createMemo(() => {
|
|
const permissionParts = createMemo(() => {
|
|
|
- if (props.stepsExpanded) return [] as { part: ToolPart; message: AssistantMessage }[]
|
|
|
|
|
|
|
+ if (props.stepsExpanded) return emptyPermissionParts
|
|
|
|
|
|
|
|
const next = nextPermission()
|
|
const next = nextPermission()
|
|
|
- if (!next) return [] as { part: ToolPart; message: AssistantMessage }[]
|
|
|
|
|
|
|
+ if (!next) return emptyPermissionParts
|
|
|
|
|
|
|
|
for (const message of assistantMessages()) {
|
|
for (const message of assistantMessages()) {
|
|
|
- const parts = data.store.part[message.id] ?? []
|
|
|
|
|
|
|
+ const parts = data.store.part[message.id] ?? emptyParts
|
|
|
for (const part of parts) {
|
|
for (const part of parts) {
|
|
|
if (part?.type !== "tool") continue
|
|
if (part?.type !== "tool") continue
|
|
|
const tool = part as ToolPart
|
|
const tool = part as ToolPart
|
|
|
if (tool.callID === next.callID) return [{ part: tool, message }]
|
|
if (tool.callID === next.callID) return [{ part: tool, message }]
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- return [] as { part: ToolPart; message: AssistantMessage }[]
|
|
|
|
|
|
|
+ return emptyPermissionParts
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const shellModePart = createMemo(() => {
|
|
const shellModePart = createMemo(() => {
|
|
@@ -224,7 +249,7 @@ export function SessionTurn(
|
|
|
const msgs = assistantMessages()
|
|
const msgs = assistantMessages()
|
|
|
if (msgs.length !== 1) return
|
|
if (msgs.length !== 1) return
|
|
|
|
|
|
|
|
- const msgParts = data.store.part[msgs[0].id] ?? []
|
|
|
|
|
|
|
+ const msgParts = data.store.part[msgs[0].id] ?? emptyParts
|
|
|
if (msgParts.length !== 1) return
|
|
if (msgParts.length !== 1) return
|
|
|
|
|
|
|
|
const assistantPart = msgParts[0]
|
|
const assistantPart = msgParts[0]
|
|
@@ -239,7 +264,7 @@ export function SessionTurn(
|
|
|
let currentTask: ToolPart | undefined
|
|
let currentTask: ToolPart | undefined
|
|
|
|
|
|
|
|
for (let mi = msgs.length - 1; mi >= 0; mi--) {
|
|
for (let mi = msgs.length - 1; mi >= 0; mi--) {
|
|
|
- const msgParts = data.store.part[msgs[mi].id] ?? []
|
|
|
|
|
|
|
+ const msgParts = data.store.part[msgs[mi].id] ?? emptyParts
|
|
|
for (let pi = msgParts.length - 1; pi >= 0; pi--) {
|
|
for (let pi = msgParts.length - 1; pi >= 0; pi--) {
|
|
|
const part = msgParts[pi]
|
|
const part = msgParts[pi]
|
|
|
if (!part) continue
|
|
if (!part) continue
|
|
@@ -266,12 +291,12 @@ export function SessionTurn(
|
|
|
: undefined
|
|
: undefined
|
|
|
|
|
|
|
|
if (taskSessionId) {
|
|
if (taskSessionId) {
|
|
|
- const taskMessages = data.store.message[taskSessionId] ?? []
|
|
|
|
|
|
|
+ const taskMessages = data.store.message[taskSessionId] ?? emptyMessages
|
|
|
for (let mi = taskMessages.length - 1; mi >= 0; mi--) {
|
|
for (let mi = taskMessages.length - 1; mi >= 0; mi--) {
|
|
|
const msg = taskMessages[mi]
|
|
const msg = taskMessages[mi]
|
|
|
if (!msg || msg.role !== "assistant") continue
|
|
if (!msg || msg.role !== "assistant") continue
|
|
|
|
|
|
|
|
- const msgParts = data.store.part[msg.id] ?? []
|
|
|
|
|
|
|
+ const msgParts = data.store.part[msg.id] ?? emptyParts
|
|
|
for (let pi = msgParts.length - 1; pi >= 0; pi--) {
|
|
for (let pi = msgParts.length - 1; pi >= 0; pi--) {
|
|
|
const part = msgParts[pi]
|
|
const part = msgParts[pi]
|
|
|
if (part) return computeStatusFromPart(part)
|
|
if (part) return computeStatusFromPart(part)
|
|
@@ -282,12 +307,7 @@ export function SessionTurn(
|
|
|
return computeStatusFromPart(last)
|
|
return computeStatusFromPart(last)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- const status = createMemo(
|
|
|
|
|
- () =>
|
|
|
|
|
- data.store.session_status[props.sessionID] ?? {
|
|
|
|
|
- type: "idle",
|
|
|
|
|
- },
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ const status = createMemo(() => data.store.session_status[props.sessionID] ?? idle)
|
|
|
const working = createMemo(() => status().type !== "idle" && isLastUserMessage())
|
|
const working = createMemo(() => status().type !== "idle" && isLastUserMessage())
|
|
|
const retry = createMemo(() => {
|
|
const retry = createMemo(() => {
|
|
|
const s = status()
|
|
const s = status()
|