mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-03 00:36:20 -04:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| effa7b45cf | |||
| b307075063 | |||
| aaf9a5d434 | |||
| e9c2f1f3f3 | |||
| 7469cba7cf | |||
| 5420702f69 | |||
| 583751ecae | |||
| d0a1b5ef96 | |||
| 42f2bc7199 | |||
| 603dae562a | |||
| 650bd76370 | |||
| 8aa3520683 | |||
| 5b5b8c57d9 | |||
| f057b22e20 | |||
| 388d40e41f | |||
| f397c92ddf | |||
| 6f9bea4e1f | |||
| 5c49b4cbfc | |||
| b746e831e2 | |||
| 2178deef91 | |||
| b1d2fb5319 |
@@ -180,3 +180,4 @@
|
||||
| 2025-12-22 | 1,262,522 (+19,847) | 1,169,121 (+10,212) | 2,431,643 (+30,059) |
|
||||
| 2025-12-23 | 1,286,548 (+24,026) | 1,186,439 (+17,318) | 2,472,987 (+41,344) |
|
||||
| 2025-12-24 | 1,309,323 (+22,775) | 1,203,767 (+17,328) | 2,513,090 (+40,103) |
|
||||
| 2025-12-25 | 1,333,032 (+23,709) | 1,217,283 (+13,516) | 2,550,315 (+37,225) |
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -77,7 +77,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -105,7 +105,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -132,7 +132,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "2.0.0",
|
||||
"@ai-sdk/openai": "2.0.2",
|
||||
@@ -156,7 +156,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -180,7 +180,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"dependencies": {
|
||||
"@opencode-ai/app": "workspace:*",
|
||||
"@solid-primitives/storage": "catalog:",
|
||||
@@ -207,7 +207,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"dependencies": {
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
@@ -236,7 +236,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -252,7 +252,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -346,7 +346,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"zod": "catalog:",
|
||||
@@ -366,7 +366,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "0.88.1",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
@@ -377,7 +377,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -390,7 +390,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -425,7 +425,7 @@
|
||||
},
|
||||
"packages/util": {
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"dependencies": {
|
||||
"zod": "catalog:",
|
||||
},
|
||||
@@ -436,7 +436,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -103,7 +103,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
imageAttachments: ImageAttachmentPart[]
|
||||
mode: "normal" | "shell"
|
||||
applyingHistory: boolean
|
||||
userHasEdited: boolean
|
||||
}>({
|
||||
popover: null,
|
||||
historyIndex: -1,
|
||||
@@ -113,7 +112,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
imageAttachments: [],
|
||||
mode: "normal",
|
||||
applyingHistory: false,
|
||||
userHasEdited: false,
|
||||
})
|
||||
|
||||
const MAX_HISTORY = 100
|
||||
@@ -150,7 +148,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
const applyHistoryPrompt = (p: Prompt, position: "start" | "end") => {
|
||||
const length = position === "start" ? 0 : promptLength(p)
|
||||
setStore("applyingHistory", true)
|
||||
setStore("userHasEdited", false)
|
||||
prompt.set(p, length)
|
||||
requestAnimationFrame(() => {
|
||||
editorRef.focus()
|
||||
@@ -279,11 +276,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (isFocused()) {
|
||||
handleInput()
|
||||
} else {
|
||||
setStore("popover", null)
|
||||
}
|
||||
if (!isFocused()) setStore("popover", null)
|
||||
})
|
||||
|
||||
const handleFileSelect = (path: string | undefined) => {
|
||||
@@ -363,7 +356,12 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
() => prompt.current(),
|
||||
(currentParts) => {
|
||||
const domParts = parseFromDOM()
|
||||
if (isPromptEqual(currentParts, domParts)) return
|
||||
const normalized = Array.from(editorRef.childNodes).every((node) => {
|
||||
if (node.nodeType === Node.TEXT_NODE) return true
|
||||
if (node.nodeType !== Node.ELEMENT_NODE) return false
|
||||
return (node as HTMLElement).dataset.type === "file"
|
||||
})
|
||||
if (normalized && isPromptEqual(currentParts, domParts)) return
|
||||
|
||||
const selection = window.getSelection()
|
||||
let cursorPosition: number | null = null
|
||||
@@ -395,34 +393,21 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
)
|
||||
|
||||
const parseFromDOM = (): Prompt => {
|
||||
const newParts: Prompt = []
|
||||
const parts: Prompt = []
|
||||
let position = 0
|
||||
let buffer = ""
|
||||
|
||||
const pushText = (content: string) => {
|
||||
const flushText = () => {
|
||||
const content = buffer.replace(/\r\n?/g, "\n")
|
||||
buffer = ""
|
||||
if (!content) return
|
||||
newParts.push({ type: "text", content, start: position, end: position + content.length })
|
||||
parts.push({ type: "text", content, start: position, end: position + content.length })
|
||||
position += content.length
|
||||
}
|
||||
|
||||
const rangeText = (range: Range) => {
|
||||
const fragment = range.cloneContents()
|
||||
const container = document.createElement("div")
|
||||
container.append(fragment)
|
||||
return container.innerText
|
||||
}
|
||||
|
||||
const files = Array.from(editorRef.querySelectorAll<HTMLElement>("[data-type=file]"))
|
||||
let last: HTMLElement | undefined
|
||||
|
||||
files.forEach((file) => {
|
||||
const before = document.createRange()
|
||||
before.selectNodeContents(editorRef)
|
||||
if (last) before.setStartAfter(last)
|
||||
before.setEndBefore(file)
|
||||
pushText(rangeText(before))
|
||||
|
||||
const pushFile = (file: HTMLElement) => {
|
||||
const content = file.textContent ?? ""
|
||||
newParts.push({
|
||||
parts.push({
|
||||
type: "file",
|
||||
path: file.dataset.path!,
|
||||
content,
|
||||
@@ -430,16 +415,44 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
end: position + content.length,
|
||||
})
|
||||
position += content.length
|
||||
last = file
|
||||
}
|
||||
|
||||
const visit = (node: Node) => {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
buffer += node.textContent ?? ""
|
||||
return
|
||||
}
|
||||
if (node.nodeType !== Node.ELEMENT_NODE) return
|
||||
|
||||
const el = node as HTMLElement
|
||||
if (el.dataset.type === "file") {
|
||||
flushText()
|
||||
pushFile(el)
|
||||
return
|
||||
}
|
||||
if (el.tagName === "BR") {
|
||||
buffer += "\n"
|
||||
return
|
||||
}
|
||||
|
||||
for (const child of Array.from(el.childNodes)) {
|
||||
visit(child)
|
||||
}
|
||||
}
|
||||
|
||||
const children = Array.from(editorRef.childNodes)
|
||||
children.forEach((child, index) => {
|
||||
const isBlock = child.nodeType === Node.ELEMENT_NODE && ["DIV", "P"].includes((child as HTMLElement).tagName)
|
||||
visit(child)
|
||||
if (isBlock && index < children.length - 1) {
|
||||
buffer += "\n"
|
||||
}
|
||||
})
|
||||
|
||||
const after = document.createRange()
|
||||
after.selectNodeContents(editorRef)
|
||||
if (last) after.setStartAfter(last)
|
||||
pushText(rangeText(after))
|
||||
flushText()
|
||||
|
||||
if (newParts.length === 0) newParts.push(...DEFAULT_PROMPT)
|
||||
return newParts
|
||||
if (parts.length === 0) parts.push(...DEFAULT_PROMPT)
|
||||
return parts
|
||||
}
|
||||
|
||||
const handleInput = () => {
|
||||
@@ -452,7 +465,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
|
||||
if (shouldReset) {
|
||||
setStore("popover", null)
|
||||
setStore("userHasEdited", false)
|
||||
if (store.historyIndex >= 0 && !store.applyingHistory) {
|
||||
setStore("historyIndex", -1)
|
||||
setStore("savedPrompt", null)
|
||||
@@ -487,10 +499,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
setStore("savedPrompt", null)
|
||||
}
|
||||
|
||||
if (!store.applyingHistory) {
|
||||
setStore("userHasEdited", true)
|
||||
}
|
||||
|
||||
prompt.set(rawParts, cursorPosition)
|
||||
}
|
||||
|
||||
@@ -516,27 +524,39 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
const gap = document.createTextNode(" ")
|
||||
const range = selection.getRangeAt(0)
|
||||
|
||||
if (atMatch) {
|
||||
let runningLength = 0
|
||||
const setEdge = (edge: "start" | "end", offset: number) => {
|
||||
let remaining = offset
|
||||
const nodes = Array.from(editorRef.childNodes)
|
||||
|
||||
const walker = document.createTreeWalker(editorRef, NodeFilter.SHOW_TEXT, null)
|
||||
let currentNode = walker.nextNode()
|
||||
while (currentNode) {
|
||||
const textContent = currentNode.textContent || ""
|
||||
if (runningLength + textContent.length >= atMatch.index!) {
|
||||
const localStart = atMatch.index! - runningLength
|
||||
const localEnd = cursorPosition - runningLength
|
||||
if (currentNode === range.startContainer || runningLength + textContent.length >= cursorPosition) {
|
||||
range.setStart(currentNode, localStart)
|
||||
range.setEnd(currentNode, Math.min(localEnd, textContent.length))
|
||||
break
|
||||
}
|
||||
for (const node of nodes) {
|
||||
const length = node.textContent?.length ?? 0
|
||||
const isText = node.nodeType === Node.TEXT_NODE
|
||||
const isFile = node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).dataset.type === "file"
|
||||
|
||||
if (isText && remaining <= length) {
|
||||
if (edge === "start") range.setStart(node, remaining)
|
||||
if (edge === "end") range.setEnd(node, remaining)
|
||||
return
|
||||
}
|
||||
runningLength += textContent.length
|
||||
currentNode = walker.nextNode()
|
||||
|
||||
if (isFile && remaining <= length) {
|
||||
if (edge === "start" && remaining === 0) range.setStartBefore(node)
|
||||
if (edge === "start" && remaining > 0) range.setStartAfter(node)
|
||||
if (edge === "end" && remaining === 0) range.setEndBefore(node)
|
||||
if (edge === "end" && remaining > 0) range.setEndAfter(node)
|
||||
return
|
||||
}
|
||||
|
||||
remaining -= length
|
||||
}
|
||||
}
|
||||
|
||||
if (atMatch) {
|
||||
const start = atMatch.index ?? cursorPosition - atMatch[0].length
|
||||
setEdge("start", start)
|
||||
setEdge("end", cursorPosition)
|
||||
}
|
||||
|
||||
range.deleteContents()
|
||||
range.insertNode(gap)
|
||||
range.insertNode(pill)
|
||||
@@ -584,8 +604,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
}
|
||||
|
||||
const navigateHistory = (direction: "up" | "down") => {
|
||||
if (store.userHasEdited) return false
|
||||
|
||||
const entries = store.mode === "shell" ? shellHistory.entries : history.entries
|
||||
const current = store.historyIndex
|
||||
|
||||
@@ -692,6 +710,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === "Enter" && event.shiftKey) {
|
||||
addPart({ type: "text", content: "\n", start: 0, end: 0 })
|
||||
event.preventDefault()
|
||||
return
|
||||
}
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
handleSubmit(event)
|
||||
}
|
||||
@@ -717,7 +740,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
addToHistory(currentPrompt, store.mode)
|
||||
setStore("historyIndex", -1)
|
||||
setStore("savedPrompt", null)
|
||||
setStore("userHasEdited", false)
|
||||
|
||||
let existing = info()
|
||||
if (!existing) {
|
||||
@@ -967,18 +989,21 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
onInput={handleInput}
|
||||
onKeyDown={handleKeyDown}
|
||||
classList={{
|
||||
"w-full px-5 py-3 text-14-regular text-text-strong focus:outline-none whitespace-pre-wrap": true,
|
||||
"[&>[data-type=file]]:text-icon-info-active": true,
|
||||
"w-full px-5 py-3 pr-12 text-14-regular text-text-strong focus:outline-none whitespace-pre-wrap": true,
|
||||
"[&_[data-type=file]]:text-icon-info-active": true,
|
||||
"font-mono!": store.mode === "shell",
|
||||
}}
|
||||
/>
|
||||
<Show when={!prompt.dirty() && store.imageAttachments.length === 0}>
|
||||
<div class="absolute top-0 inset-x-0 px-5 py-3 text-14-regular text-text-weak pointer-events-none whitespace-nowrap truncate">
|
||||
<div class="absolute top-0 inset-x-0 px-5 py-3 pr-12 text-14-regular text-text-weak pointer-events-none whitespace-nowrap truncate">
|
||||
{store.mode === "shell"
|
||||
? "Enter shell command..."
|
||||
: `Ask anything... "${PLACEHOLDERS[store.placeholder]}"`}
|
||||
</div>
|
||||
</Show>
|
||||
<div class="absolute top-4.5 right-4">
|
||||
<SessionContextUsage />
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative p-3 flex items-center justify-between">
|
||||
<div class="flex items-center justify-start gap-1">
|
||||
@@ -1035,7 +1060,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
</Tooltip>
|
||||
</Match>
|
||||
</Switch>
|
||||
<SessionContextUsage />
|
||||
</div>
|
||||
<div class="flex items-center gap-1 absolute right-2 bottom-2">
|
||||
<input
|
||||
|
||||
@@ -34,28 +34,27 @@ export function SessionContextUsage() {
|
||||
<Show when={context?.()}>
|
||||
{(ctx) => (
|
||||
<Tooltip
|
||||
openDelay={300}
|
||||
value={
|
||||
<div class="flex flex-col gap-1 p-2">
|
||||
<div class="flex justify-between gap-4">
|
||||
<span class="text-text-weaker">Tokens</span>
|
||||
<span class="text-text-strong">{ctx().tokens}</span>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex gap-3">
|
||||
<span class="opacity-70 text-right flex-1">Tokens</span>
|
||||
<span class="text-left flex-1">{ctx().tokens}</span>
|
||||
</div>
|
||||
<div class="flex justify-between gap-4">
|
||||
<span class="text-text-weaker">Usage</span>
|
||||
<span class="text-text-strong">{ctx().percentage ?? 0}%</span>
|
||||
<div class="flex gap-3">
|
||||
<span class="opacity-70 text-right flex-1">Usage</span>
|
||||
<span class="text-left flex-1">{ctx().percentage ?? 0}%</span>
|
||||
</div>
|
||||
<div class="flex justify-between gap-4">
|
||||
<span class="text-text-weaker">Cost</span>
|
||||
<span class="text-text-strong">{cost()}</span>
|
||||
<div class="flex gap-3">
|
||||
<span class="opacity-70 text-right flex-1">Cost</span>
|
||||
<span class="text-left flex-1">{cost()}</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
placement="top"
|
||||
>
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="text-12-medium text-text-weak">{`${ctx().percentage ?? 0}%`}</span>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<ProgressCircle size={16} strokeWidth={2} percentage={ctx().percentage ?? 0} />
|
||||
{/* <span class="text-12-medium text-text-weak">{`${ctx().percentage ?? 0}%`}</span> */}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
@@ -215,13 +215,13 @@ function createGlobalSync() {
|
||||
break
|
||||
}
|
||||
case "session.diff":
|
||||
setStore("session_diff", event.properties.sessionID, event.properties.diff)
|
||||
setStore("session_diff", event.properties.sessionID, reconcile(event.properties.diff, { key: "file" }))
|
||||
break
|
||||
case "todo.updated":
|
||||
setStore("todo", event.properties.sessionID, event.properties.todos)
|
||||
setStore("todo", event.properties.sessionID, reconcile(event.properties.todos))
|
||||
break
|
||||
case "session.status": {
|
||||
setStore("session_status", event.properties.sessionID, event.properties.status)
|
||||
setStore("session_status", event.properties.sessionID, reconcile(event.properties.status))
|
||||
break
|
||||
}
|
||||
case "message.updated": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createStore, produce, reconcile } from "solid-js/store"
|
||||
import { batch, createEffect, createMemo } from "solid-js"
|
||||
import { batch, createMemo } from "solid-js"
|
||||
import { filter, firstBy, flat, groupBy, mapValues, pipe, uniqueBy, values } from "remeda"
|
||||
import type { FileContent, FileNode, Model, Provider, File as FileStatus } from "@opencode-ai/sdk/v2"
|
||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||
@@ -62,24 +62,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
}
|
||||
}
|
||||
|
||||
// Automatically update model when agent changes
|
||||
createEffect(() => {
|
||||
const value = agent.current()
|
||||
if (value.model) {
|
||||
if (isModelValid(value.model))
|
||||
model.set({
|
||||
providerID: value.model.providerID,
|
||||
modelID: value.model.modelID,
|
||||
})
|
||||
// else
|
||||
// toast.show({
|
||||
// type: "warning",
|
||||
// message: `Agent ${value.name}'s configured model ${value.model.providerID}/${value.model.modelID} is not valid`,
|
||||
// duration: 3000,
|
||||
// })
|
||||
}
|
||||
})
|
||||
|
||||
const agent = (() => {
|
||||
const list = createMemo(() => sync.data.agent.filter((x) => x.mode !== "subagent" && !x.hidden))
|
||||
const [store, setStore] = createStore<{
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
ParentProps,
|
||||
Show,
|
||||
Switch,
|
||||
untrack,
|
||||
type JSX,
|
||||
} from "solid-js"
|
||||
import { DateTime } from "luxon"
|
||||
@@ -323,7 +324,7 @@ export default function Layout(props: ParentProps) {
|
||||
const id = params.id
|
||||
setStore("lastSession", directory, id)
|
||||
notification.session.markViewed(id)
|
||||
layout.projects.expand(directory)
|
||||
untrack(() => layout.projects.expand(directory))
|
||||
requestAnimationFrame(() => scrollToSession(id))
|
||||
})
|
||||
|
||||
@@ -472,7 +473,12 @@ export default function Layout(props: ParentProps) {
|
||||
class="flex flex-col min-w-0 text-left w-full focus:outline-none"
|
||||
>
|
||||
<div class="flex items-center self-stretch gap-6 justify-between transition-[padding] group-hover/session:pr-7 group-focus-within/session:pr-7 group-active/session:pr-7">
|
||||
<span class="text-14-regular text-text-strong overflow-hidden text-ellipsis truncate">
|
||||
<span
|
||||
classList={{
|
||||
"text-14-regular text-text-strong overflow-hidden text-ellipsis truncate": true,
|
||||
"animate-pulse": isWorking(),
|
||||
}}
|
||||
>
|
||||
{props.session.title}
|
||||
</span>
|
||||
<div class="shrink-0 group-hover/session:hidden group-active/session:hidden group-focus-within/session:hidden">
|
||||
@@ -513,7 +519,15 @@ export default function Layout(props: ParentProps) {
|
||||
</A>
|
||||
</Tooltip>
|
||||
<div class="hidden group-hover/session:flex group-active/session:flex group-focus-within/session:flex text-text-base gap-1 items-center absolute top-1 right-1">
|
||||
<Tooltip placement={props.mobile ? "bottom" : "right"} value="Archive session">
|
||||
<Tooltip
|
||||
placement={props.mobile ? "bottom" : "right"}
|
||||
value={
|
||||
<div class="flex items-center gap-2">
|
||||
<span>Archive session</span>
|
||||
<span class="text-icon-base text-12-medium">{command.keybind("session.archive")}</span>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<IconButton icon="archive" variant="ghost" onClick={() => archiveSession(props.session)} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
@@ -578,7 +592,15 @@ export default function Layout(props: ParentProps) {
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu>
|
||||
<Tooltip placement="top" value="New session">
|
||||
<Tooltip
|
||||
placement="top"
|
||||
value={
|
||||
<div class="flex items-center gap-2">
|
||||
<span>New session</span>
|
||||
<span class="text-icon-base text-12-medium">{command.keybind("session.new")}</span>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<IconButton as={A} href={`${slug()}/session`} icon="plus-small" variant="ghost" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsgo --noEmit",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"private": true,
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsgo -b",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "opencode"
|
||||
name = "OpenCode"
|
||||
description = "The open source coding agent."
|
||||
version = "1.0.199"
|
||||
version = "1.0.202"
|
||||
schema_version = 1
|
||||
authors = ["Anomaly"]
|
||||
repository = "https://github.com/sst/opencode"
|
||||
@@ -11,26 +11,26 @@ name = "OpenCode"
|
||||
icon = "./icons/opencode.svg"
|
||||
|
||||
[agent_servers.opencode.targets.darwin-aarch64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.199/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.202/opencode-darwin-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.darwin-x86_64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.199/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.202/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.199/opencode-linux-arm64.tar.gz"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.202/opencode-linux-arm64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-x86_64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.199/opencode-linux-x64.tar.gz"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.202/opencode-linux-x64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.windows-x86_64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.199/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.202/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
|
||||
@@ -95,7 +95,6 @@ export namespace ToolRegistry {
|
||||
ReadTool,
|
||||
GlobTool,
|
||||
GrepTool,
|
||||
ListTool,
|
||||
EditTool,
|
||||
WriteTool,
|
||||
TaskTool,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsgo --noEmit",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsgo --noEmit",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "bun run src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./*": "./src/components/*.tsx",
|
||||
|
||||
@@ -6,6 +6,7 @@ const icons = {
|
||||
"arrow-left": `<path d="M8.33464 4.58398L2.91797 10.0007L8.33464 15.4173M3.33464 10.0007H17.0846" stroke="currentColor" stroke-linecap="square"/>`,
|
||||
archive: `<path d="M16.8747 6.24935H17.3747V5.74935H16.8747V6.24935ZM16.8747 16.8743V17.3743H17.3747V16.8743H16.8747ZM3.12467 16.8743H2.62467V17.3743H3.12467V16.8743ZM3.12467 6.24935V5.74935H2.62467V6.24935H3.12467ZM2.08301 2.91602V2.41602H1.58301V2.91602H2.08301ZM17.9163 2.91602H18.4163V2.41602H17.9163V2.91602ZM17.9163 6.24935V6.74935H18.4163V6.24935H17.9163ZM2.08301 6.24935H1.58301V6.74935H2.08301V6.24935ZM8.33301 9.08268H7.83301V10.0827H8.33301V9.58268V9.08268ZM11.6663 10.0827H12.1663V9.08268H11.6663V9.58268V10.0827ZM16.8747 6.24935H16.3747V16.8743H16.8747H17.3747V6.24935H16.8747ZM16.8747 16.8743V16.3743H3.12467V16.8743V17.3743H16.8747V16.8743ZM3.12467 16.8743H3.62467V6.24935H3.12467H2.62467V16.8743H3.12467ZM3.12467 6.24935V6.74935H16.8747V6.24935V5.74935H3.12467V6.24935ZM2.08301 2.91602V3.41602H17.9163V2.91602V2.41602H2.08301V2.91602ZM17.9163 2.91602H17.4163V6.24935H17.9163H18.4163V2.91602H17.9163ZM17.9163 6.24935V5.74935H2.08301V6.24935V6.74935H17.9163V6.24935ZM2.08301 6.24935H2.58301V2.91602H2.08301H1.58301V6.24935H2.08301ZM8.33301 9.58268V10.0827H11.6663V9.58268V9.08268H8.33301V9.58268Z" fill="currentColor"/>`,
|
||||
"bubble-5": `<path d="M18.3327 9.99935C18.3327 5.57227 15.0919 2.91602 9.99935 2.91602C4.90676 2.91602 1.66602 5.57227 1.66602 9.99935C1.66602 11.1487 2.45505 13.1006 2.57637 13.3939C2.58707 13.4197 2.59766 13.4434 2.60729 13.4697C2.69121 13.6987 3.04209 14.9354 1.66602 16.7674C3.51787 17.6528 5.48453 16.1973 5.48453 16.1973C6.84518 16.9193 8.46417 17.0827 9.99935 17.0827C15.0919 17.0827 18.3327 14.4264 18.3327 9.99935Z" stroke="currentColor" stroke-linecap="square"/>`,
|
||||
brain: `<path d="M13.332 8.7487C11.4911 8.7487 9.9987 7.25631 9.9987 5.41536M6.66536 11.2487C8.50631 11.2487 9.9987 12.7411 9.9987 14.582M9.9987 2.78209L9.9987 17.0658M16.004 15.0475C17.1255 14.5876 17.9154 13.4849 17.9154 12.1978C17.9154 11.3363 17.5615 10.5575 16.9913 9.9987C17.5615 9.43991 17.9154 8.66108 17.9154 7.79962C17.9154 6.21199 16.7136 4.90504 15.1702 4.73878C14.7858 3.21216 13.4039 2.08203 11.758 2.08203C11.1171 2.08203 10.5162 2.25337 9.9987 2.55275C9.48117 2.25337 8.88032 2.08203 8.23944 2.08203C6.59353 2.08203 5.21157 3.21216 4.82722 4.73878C3.28377 4.90504 2.08203 6.21199 2.08203 7.79962C2.08203 8.66108 2.43585 9.43991 3.00609 9.9987C2.43585 10.5575 2.08203 11.3363 2.08203 12.1978C2.08203 13.4849 2.87191 14.5876 3.99339 15.0475C4.46688 16.7033 5.9917 17.9154 7.79962 17.9154C8.61335 17.9154 9.36972 17.6698 9.9987 17.2488C10.6277 17.6698 11.384 17.9154 12.1978 17.9154C14.0057 17.9154 15.5305 16.7033 16.004 15.0475Z" stroke="currentColor"/>`,
|
||||
"bullet-list": `<path d="M9.58329 13.7497H17.0833M9.58329 6.24967H17.0833M6.24996 6.24967C6.24996 7.17015 5.50377 7.91634 4.58329 7.91634C3.66282 7.91634 2.91663 7.17015 2.91663 6.24967C2.91663 5.3292 3.66282 4.58301 4.58329 4.58301C5.50377 4.58301 6.24996 5.3292 6.24996 6.24967ZM6.24996 13.7497C6.24996 14.6701 5.50377 15.4163 4.58329 15.4163C3.66282 15.4163 2.91663 14.6701 2.91663 13.7497C2.91663 12.8292 3.66282 12.083 4.58329 12.083C5.50377 12.083 6.24996 12.8292 6.24996 13.7497Z" stroke="currentColor" stroke-linecap="square"/>`,
|
||||
"check-small": `<path d="M6.5 11.4412L8.97059 13.5L13.5 6.5" stroke="currentColor" stroke-linecap="square"/>`,
|
||||
"chevron-down": `<path d="M6.6665 8.33325L9.99984 11.6666L13.3332 8.33325" stroke="currentColor" stroke-linecap="square"/>`,
|
||||
|
||||
@@ -1,21 +1,6 @@
|
||||
import { useMarked } from "../context/marked"
|
||||
import { ComponentProps, createResource, splitProps } from "solid-js"
|
||||
|
||||
function strip(text: string): string {
|
||||
const trimmed = text.trim()
|
||||
const match = trimmed.match(/^<([A-Za-z]\w*)>/)
|
||||
if (!match) return text
|
||||
|
||||
const tagName = match[1]
|
||||
const closingTag = `</${tagName}>`
|
||||
if (trimmed.endsWith(closingTag)) {
|
||||
const content = trimmed.slice(match[0].length, -closingTag.length)
|
||||
return content.trim()
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
export function Markdown(
|
||||
props: ComponentProps<"div"> & {
|
||||
text: string
|
||||
@@ -26,7 +11,7 @@ export function Markdown(
|
||||
const [local, others] = splitProps(props, ["text", "class", "classList"])
|
||||
const marked = useMarked()
|
||||
const [html] = createResource(
|
||||
() => strip(local.text),
|
||||
() => local.text,
|
||||
async (markdown) => {
|
||||
return marked.parse(markdown)
|
||||
},
|
||||
|
||||
@@ -72,14 +72,10 @@
|
||||
}
|
||||
|
||||
[data-slot="user-message-text"] {
|
||||
display: -webkit-box;
|
||||
white-space: pre-wrap;
|
||||
line-clamp: 3;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
background: var(--surface-inset-base);
|
||||
padding: 2px 6px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
border: 0.5px solid var(--border-weak-base);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
[data-component="radio-group"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(var(--spacing) * 2);
|
||||
|
||||
[data-slot="radio-group-wrapper"] {
|
||||
all: unset;
|
||||
background-color: var(--surface-base);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: inset 0 0 0 1px var(--border-weak-base);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
[data-slot="radio-group-items"] {
|
||||
display: inline-flex;
|
||||
list-style: none;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
[data-slot="radio-group-indicator"] {
|
||||
background: var(--button-secondary-base);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow:
|
||||
var(--shadow-xs),
|
||||
inset 0 0 0 var(--indicator-focus-width, 0px) var(--border-selected),
|
||||
inset 0 0 0 1px var(--border-base);
|
||||
content: "";
|
||||
opacity: var(--indicator-opacity, 1);
|
||||
position: absolute;
|
||||
transition:
|
||||
opacity 300ms ease-in-out,
|
||||
box-shadow 100ms ease-in-out,
|
||||
width 150ms ease,
|
||||
height 150ms ease,
|
||||
transform 150ms ease;
|
||||
}
|
||||
|
||||
[data-slot="radio-group-item"] {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Separator between items */
|
||||
[data-slot="radio-group-item"]:not(:first-of-type)::before {
|
||||
background: var(--border-weak-base);
|
||||
border-radius: var(--radius-xs);
|
||||
content: "";
|
||||
inset: 6px 0;
|
||||
position: absolute;
|
||||
transition: opacity 150ms ease;
|
||||
width: 1px;
|
||||
transform: translateX(-0.5px);
|
||||
}
|
||||
|
||||
/* Hide separator when item or previous item is checked */
|
||||
[data-slot="radio-group-item"]:has([data-slot="radio-group-item-input"][data-checked])::before,
|
||||
[data-slot="radio-group-item"]:has([data-slot="radio-group-item-input"][data-checked])
|
||||
+ [data-slot="radio-group-item"]::before {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
[data-slot="radio-group-item-label"] {
|
||||
color: var(--text-weak);
|
||||
font-family: var(--font-family-sans);
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-medium);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: calc(var(--spacing) * 1);
|
||||
line-height: 1;
|
||||
padding: 6px 12px;
|
||||
place-content: center;
|
||||
position: relative;
|
||||
transition-duration: 150ms;
|
||||
transition-property: color, opacity;
|
||||
transition-timing-function: ease-in-out;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
[data-slot="radio-group-item-input"] {
|
||||
all: unset;
|
||||
}
|
||||
|
||||
/* Checked state */
|
||||
[data-slot="radio-group-item-input"][data-checked] + [data-slot="radio-group-item-label"] {
|
||||
color: var(--text-strong);
|
||||
}
|
||||
|
||||
/* Disabled state */
|
||||
[data-slot="radio-group-item-input"][data-disabled] + [data-slot="radio-group-item-label"] {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Hover state for unchecked, enabled items */
|
||||
[data-slot="radio-group-item-input"]:not([data-checked], [data-disabled]) + [data-slot="radio-group-item-label"] {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
[data-slot="radio-group-item-input"]:not([data-checked], [data-disabled])
|
||||
+ [data-slot="radio-group-item-label"]:hover {
|
||||
color: var(--text-base);
|
||||
}
|
||||
|
||||
[data-slot="radio-group-item-input"]:not([data-checked], [data-disabled])
|
||||
+ [data-slot="radio-group-item-label"]:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* Focus state */
|
||||
[data-slot="radio-group-wrapper"]:has([data-slot="radio-group-item-input"]:focus-visible)
|
||||
[data-slot="radio-group-indicator"] {
|
||||
--indicator-focus-width: 2px;
|
||||
}
|
||||
|
||||
/* Hide indicator when nothing is checked */
|
||||
[data-slot="radio-group-wrapper"]:not(:has([data-slot="radio-group-item-input"][data-checked]))
|
||||
[data-slot="radio-group-indicator"] {
|
||||
--indicator-opacity: 0;
|
||||
}
|
||||
|
||||
/* Vertical orientation */
|
||||
&[aria-orientation="vertical"] [data-slot="radio-group-items"] {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&[aria-orientation="vertical"] [data-slot="radio-group-item"]:not(:first-of-type)::before {
|
||||
height: 1px;
|
||||
width: auto;
|
||||
inset: 0 6px;
|
||||
transform: translateY(-0.5px);
|
||||
}
|
||||
|
||||
/* Small size variant */
|
||||
&[data-size="small"] {
|
||||
[data-slot="radio-group-item-label"] {
|
||||
font-size: 12px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
[data-slot="radio-group-item"]:not(:first-of-type)::before {
|
||||
inset: 4px 0;
|
||||
}
|
||||
|
||||
&[aria-orientation="vertical"] [data-slot="radio-group-item"]:not(:first-of-type)::before {
|
||||
inset: 0 4px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Disabled root state */
|
||||
&[data-disabled] {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { SegmentedControl as Kobalte } from "@kobalte/core/segmented-control"
|
||||
import { For, splitProps } from "solid-js"
|
||||
import type { ComponentProps, JSX } from "solid-js"
|
||||
|
||||
export type RadioGroupProps<T> = Omit<
|
||||
ComponentProps<typeof Kobalte>,
|
||||
"value" | "defaultValue" | "onChange" | "children"
|
||||
> & {
|
||||
options: T[]
|
||||
current?: T
|
||||
defaultValue?: T
|
||||
value?: (x: T) => string
|
||||
label?: (x: T) => JSX.Element | string
|
||||
onSelect?: (value: T | undefined) => void
|
||||
class?: ComponentProps<"div">["class"]
|
||||
classList?: ComponentProps<"div">["classList"]
|
||||
size?: "small" | "medium"
|
||||
}
|
||||
|
||||
export function RadioGroup<T>(props: RadioGroupProps<T>) {
|
||||
const [local, others] = splitProps(props, [
|
||||
"class",
|
||||
"classList",
|
||||
"options",
|
||||
"current",
|
||||
"defaultValue",
|
||||
"value",
|
||||
"label",
|
||||
"onSelect",
|
||||
"size",
|
||||
])
|
||||
|
||||
const getValue = (item: T): string => {
|
||||
if (local.value) return local.value(item)
|
||||
return String(item)
|
||||
}
|
||||
|
||||
const getLabel = (item: T): JSX.Element | string => {
|
||||
if (local.label) return local.label(item)
|
||||
return String(item)
|
||||
}
|
||||
|
||||
const findOption = (v: string): T | undefined => {
|
||||
return local.options.find((opt) => getValue(opt) === v)
|
||||
}
|
||||
|
||||
return (
|
||||
<Kobalte
|
||||
{...others}
|
||||
data-component="radio-group"
|
||||
data-size={local.size ?? "medium"}
|
||||
classList={{
|
||||
...(local.classList ?? {}),
|
||||
[local.class ?? ""]: !!local.class,
|
||||
}}
|
||||
value={local.current ? getValue(local.current) : undefined}
|
||||
defaultValue={local.defaultValue ? getValue(local.defaultValue) : undefined}
|
||||
onChange={(v) => local.onSelect?.(findOption(v))}
|
||||
>
|
||||
<div role="presentation" data-slot="radio-group-wrapper">
|
||||
<Kobalte.Indicator data-slot="radio-group-indicator" />
|
||||
<div role="presentation" data-slot="radio-group-items">
|
||||
<For each={local.options}>
|
||||
{(option) => (
|
||||
<Kobalte.Item value={getValue(option)} data-slot="radio-group-item">
|
||||
<Kobalte.ItemInput data-slot="radio-group-item-input" />
|
||||
<Kobalte.ItemLabel data-slot="radio-group-item-label">{getLabel(option)}</Kobalte.ItemLabel>
|
||||
</Kobalte.Item>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</Kobalte>
|
||||
)
|
||||
}
|
||||
@@ -26,7 +26,7 @@
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
min-width: 0;
|
||||
gap: 42px;
|
||||
gap: 28px;
|
||||
overflow-anchor: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -124,77 +124,96 @@ export function SessionTurn(
|
||||
return allMessages().filter((m) => m.role === "assistant" && m.parentID === msg.id) as AssistantMessage[]
|
||||
})
|
||||
|
||||
const assistantParts = createMemo(() => {
|
||||
const result: PartType[] = []
|
||||
for (const m of assistantMessages()) {
|
||||
const msgParts = data.store.part[m.id]
|
||||
if (msgParts) {
|
||||
for (const p of msgParts) {
|
||||
if (p) result.push(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
const lastAssistantMessage = createMemo(() => assistantMessages().at(-1))
|
||||
|
||||
const error = createMemo(() => assistantMessages().find((m) => m.error)?.error)
|
||||
|
||||
const lastTextPart = createMemo(() => {
|
||||
const ap = assistantParts()
|
||||
for (let i = ap.length - 1; i >= 0; i--) {
|
||||
if (ap[i]?.type === "text") return ap[i]
|
||||
const msgs = assistantMessages()
|
||||
for (let mi = msgs.length - 1; mi >= 0; mi--) {
|
||||
const msgParts = data.store.part[msgs[mi].id] ?? []
|
||||
for (let pi = msgParts.length - 1; pi >= 0; pi--) {
|
||||
const part = msgParts[pi]
|
||||
if (part?.type === "text") return part as TextPart
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
|
||||
const hasSteps = createMemo(() => assistantParts().some((p) => p?.type === "tool"))
|
||||
|
||||
const isShellMode = createMemo(() => {
|
||||
const p = parts()
|
||||
const ap = assistantParts()
|
||||
if (p.every((part) => part?.type === "text" && part?.synthetic) && ap.length === 1) {
|
||||
const assistantPart = ap[0]
|
||||
if (assistantPart?.type === "tool" && assistantPart?.tool === "bash") return true
|
||||
const hasSteps = createMemo(() => {
|
||||
for (const m of assistantMessages()) {
|
||||
const msgParts = data.store.part[m.id]
|
||||
if (!msgParts) continue
|
||||
for (const p of msgParts) {
|
||||
if (p?.type === "tool") return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
const rawStatus = createMemo(() => {
|
||||
const ap = assistantParts()
|
||||
const currentTask = ap.findLast(
|
||||
(p) =>
|
||||
p &&
|
||||
p.type === "tool" &&
|
||||
p.tool === "task" &&
|
||||
p.state &&
|
||||
"metadata" in p.state &&
|
||||
p.state.metadata &&
|
||||
p.state.metadata.sessionId &&
|
||||
p.state.status === "running",
|
||||
) as ToolPart | undefined
|
||||
const shellModePart = createMemo(() => {
|
||||
const p = parts()
|
||||
if (!p.every((part) => part?.type === "text" && part?.synthetic)) return
|
||||
|
||||
let resolvedParts = ap
|
||||
if (currentTask?.state && "metadata" in currentTask.state && currentTask.state.metadata?.sessionId) {
|
||||
const taskMessages = data.store.message[currentTask.state.metadata.sessionId as string]?.filter(
|
||||
(m) => m.role === "assistant",
|
||||
)
|
||||
if (taskMessages) {
|
||||
const taskParts: PartType[] = []
|
||||
for (const m of taskMessages) {
|
||||
const msgParts = data.store.part[m.id]
|
||||
if (msgParts) {
|
||||
for (const p of msgParts) {
|
||||
if (p) taskParts.push(p)
|
||||
}
|
||||
}
|
||||
const msgs = assistantMessages()
|
||||
if (msgs.length !== 1) return
|
||||
|
||||
const msgParts = data.store.part[msgs[0].id] ?? []
|
||||
if (msgParts.length !== 1) return
|
||||
|
||||
const assistantPart = msgParts[0]
|
||||
if (assistantPart?.type === "tool" && assistantPart.tool === "bash") return assistantPart
|
||||
})
|
||||
|
||||
const isShellMode = createMemo(() => !!shellModePart())
|
||||
|
||||
const rawStatus = createMemo(() => {
|
||||
const msgs = assistantMessages()
|
||||
let last: PartType | undefined
|
||||
let currentTask: ToolPart | undefined
|
||||
|
||||
for (let mi = msgs.length - 1; mi >= 0; mi--) {
|
||||
const msgParts = data.store.part[msgs[mi].id] ?? []
|
||||
for (let pi = msgParts.length - 1; pi >= 0; pi--) {
|
||||
const part = msgParts[pi]
|
||||
if (!part) continue
|
||||
if (!last) last = part
|
||||
|
||||
if (
|
||||
part.type === "tool" &&
|
||||
part.tool === "task" &&
|
||||
part.state &&
|
||||
"metadata" in part.state &&
|
||||
part.state.metadata?.sessionId &&
|
||||
part.state.status === "running"
|
||||
) {
|
||||
currentTask = part as ToolPart
|
||||
break
|
||||
}
|
||||
}
|
||||
if (currentTask) break
|
||||
}
|
||||
|
||||
const taskSessionId =
|
||||
currentTask?.state && "metadata" in currentTask.state
|
||||
? (currentTask.state.metadata?.sessionId as string | undefined)
|
||||
: undefined
|
||||
|
||||
if (taskSessionId) {
|
||||
const taskMessages = data.store.message[taskSessionId] ?? []
|
||||
for (let mi = taskMessages.length - 1; mi >= 0; mi--) {
|
||||
const msg = taskMessages[mi]
|
||||
if (!msg || msg.role !== "assistant") continue
|
||||
|
||||
const msgParts = data.store.part[msg.id] ?? []
|
||||
for (let pi = msgParts.length - 1; pi >= 0; pi--) {
|
||||
const part = msgParts[pi]
|
||||
if (part) return computeStatusFromPart(part)
|
||||
}
|
||||
if (taskParts.length > 0) resolvedParts = taskParts
|
||||
}
|
||||
}
|
||||
|
||||
return computeStatusFromPart(resolvedParts.at(-1))
|
||||
return computeStatusFromPart(last)
|
||||
})
|
||||
|
||||
const status = createMemo(
|
||||
@@ -367,7 +386,7 @@ export function SessionTurn(
|
||||
>
|
||||
<Switch>
|
||||
<Match when={isShellMode()}>
|
||||
<Part part={assistantParts()[0]} message={msg()} defaultOpen />
|
||||
<Part part={shellModePart()!} message={msg()} defaultOpen />
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
{/* Title (sticky) */}
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
@import "../components/message-nav.css" layer(components);
|
||||
@import "../components/popover.css" layer(components);
|
||||
@import "../components/progress-circle.css" layer(components);
|
||||
@import "../components/radio-group.css" layer(components);
|
||||
@import "../components/resize-handle.css" layer(components);
|
||||
@import "../components/select.css" layer(components);
|
||||
@import "../components/spinner.css" layer(components);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/web",
|
||||
"type": "module",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "opencode",
|
||||
"displayName": "opencode",
|
||||
"description": "opencode for VS Code",
|
||||
"version": "1.0.199",
|
||||
"version": "1.0.202",
|
||||
"publisher": "sst-dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
Reference in New Issue
Block a user