Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2239228062 | ||
|
|
d4157d9a96 | ||
|
|
6e4ef585d8 | ||
|
|
e05c3b7a76 | ||
|
|
f99904bc1c | ||
|
|
b796d6763f | ||
|
|
c1250abdf8 | ||
|
|
ebe51534a1 | ||
|
|
b8bbee4718 | ||
|
|
8f852b396f | ||
|
|
ae4d089c06 |
@@ -3,7 +3,7 @@ name: deploy
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dontlook
|
||||
- dev
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
|
||||
<a href="https://www.npmjs.com/package/opencode.ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
|
||||
<a href="https://github.com/sst/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/sst/opencode/publish.yml?style=flat-square&branch=dev" /></a>
|
||||
</p>
|
||||
|
||||
@@ -27,7 +27,7 @@ AI coding agent, built for the terminal.
|
||||
curl -fsSL https://opencode.ai/install | bash
|
||||
|
||||
# Package managers
|
||||
npm i -g opencode-ai@latest # or bun/pnpm/yarn
|
||||
npm i -g opencode.ai@latest # or bun/pnpm/yarn
|
||||
brew install sst/tap/opencode # macOS
|
||||
paru -S opencode-bin # Arch Linux
|
||||
```
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"name": "opencode.ai",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@clack/prompts": "0.11.0",
|
||||
@@ -1200,7 +1200,7 @@
|
||||
|
||||
"openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
|
||||
|
||||
"opencode": ["opencode@workspace:packages/opencode"],
|
||||
"opencode.ai": ["opencode.ai@workspace:packages/opencode"],
|
||||
|
||||
"opencontrol": ["opencontrol@0.0.6", "", { "dependencies": { "@modelcontextprotocol/sdk": "1.6.1", "@tsconfig/bun": "1.0.7", "hono": "4.7.4", "zod": "3.24.2", "zod-to-json-schema": "3.24.3" }, "bin": { "opencontrol": "bin/index.mjs" } }, "sha512-QeCrpOK5D15QV8kjnGVeD/BHFLwcVr+sn4T6KKmP0WAMs2pww56e4h+eOGHb5iPOufUQXbdbBKi6WV2kk7tefQ=="],
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "opencode",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"packageManager": "bun@1.2.14",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "0.0.0",
|
||||
"name": "opencode",
|
||||
"name": "opencode.ai",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -68,7 +68,7 @@ await $`cp ./script/postinstall.mjs ./dist/${pkg.name}/postinstall.mjs`
|
||||
await Bun.file(`./dist/${pkg.name}/package.json`).write(
|
||||
JSON.stringify(
|
||||
{
|
||||
name: pkg.name + "-ai",
|
||||
name: pkg.name,
|
||||
bin: {
|
||||
[pkg.name]: `./bin/${pkg.name}`,
|
||||
},
|
||||
|
||||
@@ -101,7 +101,7 @@ const cli = yargs(hideBin(process.argv))
|
||||
})()
|
||||
|
||||
await proc.exited
|
||||
await server.stop()
|
||||
server.stop()
|
||||
|
||||
return "done"
|
||||
})
|
||||
@@ -120,7 +120,7 @@ const cli = yargs(hideBin(process.argv))
|
||||
.command(ScrapCommand)
|
||||
.command(AuthCommand)
|
||||
.command(UpgradeCommand)
|
||||
.fail((msg, err) => {
|
||||
.fail((msg) => {
|
||||
if (
|
||||
msg.startsWith("Unknown argument") ||
|
||||
msg.startsWith("Not enough non-option arguments")
|
||||
|
||||
@@ -74,7 +74,7 @@ export namespace Installation {
|
||||
|
||||
for (const check of checks) {
|
||||
const output = await check.command()
|
||||
if (output.includes("opencode-ai")) {
|
||||
if (output.includes("opencode-ai") || output.includes("opencode.ai")) {
|
||||
return check.name
|
||||
}
|
||||
}
|
||||
@@ -95,11 +95,13 @@ export namespace Installation {
|
||||
case "curl":
|
||||
return $`curl -fsSL https://opencode.ai/install | bash`
|
||||
case "npm":
|
||||
return $`npm install -g opencode-ai@${target}`
|
||||
return $`npm remove -g opencode-ai && npm install -g opencode.ai@${target}`
|
||||
case "pnpm":
|
||||
return $`pnpm install -g opencode-ai@${target}`
|
||||
return $`pnpm remove -g opencode-ai && pnpm install -g opencode.ai@${target}`
|
||||
case "bun":
|
||||
return $`bun install -g opencode-ai@${target}`
|
||||
return $`bun remove -g opencode-ai && bun install -g opencode.ai@${target}`
|
||||
case "yarn":
|
||||
return $`yarn remove -g opencode-ai && yarn add -g opencode.ai@${target}`
|
||||
default:
|
||||
throw new Error(`Unknown method: ${method}`)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Global } from "../global"
|
||||
import { lazy } from "../util/lazy"
|
||||
import { Log } from "../util/log"
|
||||
import path from "path"
|
||||
import { z } from "zod"
|
||||
@@ -64,30 +63,4 @@ export namespace ModelsDev {
|
||||
throw new Error(`Failed to fetch models.dev: ${result.statusText}`)
|
||||
await Bun.write(file, result)
|
||||
}
|
||||
|
||||
const aisdk = lazy(async () => {
|
||||
log.info("fetching ai-sdk")
|
||||
const response = await fetch(
|
||||
"https://registry.npmjs.org/-/v1/search?text=scope:@ai-sdk",
|
||||
)
|
||||
if (!response.ok)
|
||||
throw new Error(
|
||||
`Failed to fetch ai-sdk information: ${response.statusText}`,
|
||||
)
|
||||
const result = await response.json()
|
||||
log.info("found ai-sdk", result.objects.length)
|
||||
return result.objects
|
||||
.filter((obj: any) => obj.package.name.startsWith("@ai-sdk/"))
|
||||
.reduce((acc: any, obj: any) => {
|
||||
acc[obj.package.name] = obj
|
||||
return acc
|
||||
}, {})
|
||||
})
|
||||
|
||||
export async function pkg(providerID: string): Promise<[string, string]> {
|
||||
const packages = await aisdk()
|
||||
const match = packages[`@ai-sdk/${providerID}`]
|
||||
if (match) return [match.package.name, "latest"]
|
||||
return [providerID, "latest"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,9 +43,7 @@ export namespace Provider {
|
||||
for (const model of Object.values(provider.models)) {
|
||||
model.cost = {
|
||||
input: 0,
|
||||
inputCached: 0,
|
||||
output: 0,
|
||||
outputCached: 0,
|
||||
}
|
||||
}
|
||||
return {
|
||||
@@ -103,9 +101,11 @@ export namespace Provider {
|
||||
) {
|
||||
const provider = providers[id]
|
||||
if (!provider) {
|
||||
const info = database[id]
|
||||
if (!info) return
|
||||
providers[id] = {
|
||||
source,
|
||||
info: database[id],
|
||||
info,
|
||||
options,
|
||||
}
|
||||
return
|
||||
@@ -205,9 +205,7 @@ export namespace Provider {
|
||||
}
|
||||
|
||||
// load config
|
||||
for (const [providerID, provider] of Object.entries(
|
||||
config.provider ?? {},
|
||||
)) {
|
||||
for (const [providerID, provider] of configProviders) {
|
||||
mergeProvider(providerID, provider.options ?? {}, "config")
|
||||
}
|
||||
|
||||
@@ -234,8 +232,8 @@ export namespace Provider {
|
||||
const s = await state()
|
||||
const existing = s.sdk.get(provider.id)
|
||||
if (existing) return existing
|
||||
const [pkg, version] = await ModelsDev.pkg(provider.npm ?? provider.id)
|
||||
const mod = await import(await BunProc.install(pkg, version))
|
||||
const pkg = provider.npm ?? provider.id
|
||||
const mod = await import(await BunProc.install(pkg, "latest"))
|
||||
const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!]
|
||||
const loaded = fn(s.providers[provider.id]?.options)
|
||||
s.sdk.set(provider.id, loaded)
|
||||
|
||||
@@ -31,6 +31,7 @@ import { SystemPrompt } from "./system"
|
||||
import { Flag } from "../flag/flag"
|
||||
import type { ModelsDev } from "../provider/models"
|
||||
import { GlobalConfig } from "../global/config"
|
||||
import { Installation } from "../installation"
|
||||
|
||||
export namespace Session {
|
||||
const log = Log.create({ service: "session" })
|
||||
@@ -46,6 +47,7 @@ export namespace Session {
|
||||
})
|
||||
.optional(),
|
||||
title: z.string(),
|
||||
version: z.string(),
|
||||
time: z.object({
|
||||
created: z.number(),
|
||||
updated: z.number(),
|
||||
@@ -71,19 +73,30 @@ export namespace Session {
|
||||
),
|
||||
}
|
||||
|
||||
const state = App.state("session", () => {
|
||||
const sessions = new Map<string, Info>()
|
||||
const messages = new Map<string, Message.Info[]>()
|
||||
const state = App.state(
|
||||
"session",
|
||||
() => {
|
||||
const sessions = new Map<string, Info>()
|
||||
const messages = new Map<string, Message.Info[]>()
|
||||
const pending = new Map<string, AbortController>()
|
||||
|
||||
return {
|
||||
sessions,
|
||||
messages,
|
||||
}
|
||||
})
|
||||
return {
|
||||
sessions,
|
||||
messages,
|
||||
pending,
|
||||
}
|
||||
},
|
||||
async (state) => {
|
||||
for (const [_, controller] of state.pending) {
|
||||
controller.abort()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export async function create(parentID?: string) {
|
||||
const result: Info = {
|
||||
id: Identifier.descending("session"),
|
||||
version: Installation.VERSION,
|
||||
parentID,
|
||||
title:
|
||||
(parentID ? "Child session - " : "New Session - ") +
|
||||
@@ -171,10 +184,10 @@ export namespace Session {
|
||||
}
|
||||
|
||||
export function abort(sessionID: string) {
|
||||
const controller = pending.get(sessionID)
|
||||
const controller = state().pending.get(sessionID)
|
||||
if (!controller) return false
|
||||
controller.abort()
|
||||
pending.delete(sessionID)
|
||||
state().pending.delete(sessionID)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -331,6 +344,16 @@ export namespace Session {
|
||||
sessionID: input.sessionID,
|
||||
abort: abort.signal,
|
||||
messageID: next.id,
|
||||
metadata: async (val) => {
|
||||
next.metadata.tool[opts.toolCallId] = {
|
||||
...val,
|
||||
time: {
|
||||
start: 0,
|
||||
end: 0,
|
||||
},
|
||||
}
|
||||
await updateMessage(next)
|
||||
},
|
||||
})
|
||||
next.metadata!.tool![opts.toolCallId] = {
|
||||
...result.metadata,
|
||||
@@ -724,17 +747,16 @@ export namespace Session {
|
||||
await updateMessage(next)
|
||||
}
|
||||
|
||||
const pending = new Map<string, AbortController>()
|
||||
function lock(sessionID: string) {
|
||||
log.info("locking", { sessionID })
|
||||
if (pending.has(sessionID)) throw new BusyError(sessionID)
|
||||
if (state().pending.has(sessionID)) throw new BusyError(sessionID)
|
||||
const controller = new AbortController()
|
||||
pending.set(sessionID, controller)
|
||||
state().pending.set(sessionID, controller)
|
||||
return {
|
||||
signal: controller.signal,
|
||||
[Symbol.dispose]() {
|
||||
log.info("unlocking", { sessionID })
|
||||
pending.delete(sessionID)
|
||||
state().pending.delete(sessionID)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,9 @@ export const EditTool = Tool.define({
|
||||
await file.write(contentNew)
|
||||
})()
|
||||
|
||||
const diff = createTwoFilesPatch(filepath, filepath, contentOld, contentNew)
|
||||
const diff = trimDiff(
|
||||
createTwoFilesPatch(filepath, filepath, contentOld, contentNew),
|
||||
)
|
||||
|
||||
FileTimes.read(ctx.sessionID, filepath)
|
||||
|
||||
@@ -113,3 +115,39 @@ export const EditTool = Tool.define({
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
function trimDiff(diff: string): string {
|
||||
const lines = diff.split("\n")
|
||||
const contentLines = lines.filter(
|
||||
(line) =>
|
||||
(line.startsWith("+") || line.startsWith("-") || line.startsWith(" ")) &&
|
||||
!line.startsWith("---") &&
|
||||
!line.startsWith("+++"),
|
||||
)
|
||||
|
||||
if (contentLines.length === 0) return diff
|
||||
|
||||
let min = Infinity
|
||||
for (const line of contentLines) {
|
||||
const content = line.slice(1)
|
||||
if (content.trim().length > 0) {
|
||||
const match = content.match(/^(\s*)/)
|
||||
if (match) min = Math.min(min, match[1].length)
|
||||
}
|
||||
}
|
||||
if (min === Infinity || min === 0) return diff
|
||||
const trimmedLines = lines.map((line) => {
|
||||
if (
|
||||
(line.startsWith("+") || line.startsWith("-") || line.startsWith(" ")) &&
|
||||
!line.startsWith("---") &&
|
||||
!line.startsWith("+++")
|
||||
) {
|
||||
const prefix = line[0]
|
||||
const content = line.slice(1)
|
||||
return prefix + content.slice(min)
|
||||
}
|
||||
return line
|
||||
})
|
||||
|
||||
return trimmedLines.join("\n")
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import { Tool } from "./tool"
|
||||
import DESCRIPTION from "./task.txt"
|
||||
import { z } from "zod"
|
||||
import { Session } from "../session"
|
||||
import { Bus } from "../bus"
|
||||
import { Message } from "../session/message"
|
||||
|
||||
export const TaskTool = Tool.define({
|
||||
id: "opencode.task",
|
||||
@@ -17,6 +19,28 @@ export const TaskTool = Tool.define({
|
||||
const msg = await Session.getMessage(ctx.sessionID, ctx.messageID)
|
||||
const metadata = msg.metadata.assistant!
|
||||
|
||||
function summary(input: Message.Info) {
|
||||
const result = []
|
||||
|
||||
for (const part of input.parts) {
|
||||
if (part.type === "tool-invocation") {
|
||||
result.push({
|
||||
toolInvocation: part.toolInvocation,
|
||||
metadata: input.metadata.tool[part.toolInvocation.toolCallId],
|
||||
})
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const unsub = Bus.subscribe(Message.Event.Updated, async (evt) => {
|
||||
if (evt.properties.info.metadata.sessionID !== ctx.sessionID) return
|
||||
ctx.metadata({
|
||||
title: params.description,
|
||||
summary: summary(evt.properties.info),
|
||||
})
|
||||
})
|
||||
|
||||
const result = await Session.chat({
|
||||
sessionID: session.id,
|
||||
modelID: metadata.modelID,
|
||||
@@ -28,10 +52,11 @@ export const TaskTool = Tool.define({
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
unsub()
|
||||
return {
|
||||
metadata: {
|
||||
title: params.description,
|
||||
summary: summary(result),
|
||||
},
|
||||
output: result.parts.findLast((x) => x.type === "text")!.text,
|
||||
}
|
||||
|
||||
@@ -5,10 +5,11 @@ export namespace Tool {
|
||||
title: string
|
||||
[key: string]: any
|
||||
}
|
||||
export type Context = {
|
||||
export type Context<M extends Metadata = Metadata> = {
|
||||
sessionID: string
|
||||
messageID: string
|
||||
abort: AbortSignal
|
||||
metadata(meta: M): void
|
||||
}
|
||||
export interface Info<
|
||||
Parameters extends StandardSchemaV1 = StandardSchemaV1,
|
||||
|
||||
@@ -3,6 +3,12 @@ import { App } from "../../src/app/app"
|
||||
import { GlobTool } from "../../src/tool/glob"
|
||||
import { ListTool } from "../../src/tool/ls"
|
||||
|
||||
const ctx = {
|
||||
sessionID: "test",
|
||||
messageID: "",
|
||||
abort: AbortSignal.any([]),
|
||||
metadata: () => {},
|
||||
}
|
||||
describe("tool.glob", () => {
|
||||
test("truncate", async () => {
|
||||
await App.provide({ cwd: process.cwd() }, async () => {
|
||||
@@ -11,11 +17,7 @@ describe("tool.glob", () => {
|
||||
pattern: "./node_modules/**/*",
|
||||
path: undefined,
|
||||
},
|
||||
{
|
||||
sessionID: "test",
|
||||
messageID: "",
|
||||
abort: AbortSignal.any([]),
|
||||
},
|
||||
ctx,
|
||||
)
|
||||
expect(result.metadata.truncated).toBe(true)
|
||||
})
|
||||
@@ -27,11 +29,7 @@ describe("tool.glob", () => {
|
||||
pattern: "*.json",
|
||||
path: undefined,
|
||||
},
|
||||
{
|
||||
sessionID: "test",
|
||||
messageID: "",
|
||||
abort: AbortSignal.any([]),
|
||||
},
|
||||
ctx,
|
||||
)
|
||||
expect(result.metadata).toMatchObject({
|
||||
truncated: false,
|
||||
@@ -46,11 +44,7 @@ describe("tool.ls", () => {
|
||||
const result = await App.provide({ cwd: process.cwd() }, async () => {
|
||||
return await ListTool.execute(
|
||||
{ path: "./example", ignore: [".git"] },
|
||||
{
|
||||
sessionID: "test",
|
||||
messageID: "",
|
||||
abort: AbortSignal.any([]),
|
||||
},
|
||||
ctx,
|
||||
)
|
||||
})
|
||||
expect(result.output).toMatchSnapshot()
|
||||
|
||||
@@ -172,14 +172,33 @@ function flattenToolArgs(obj: any, prefix: string = ""): Array<[string, any]> {
|
||||
return entries
|
||||
}
|
||||
|
||||
export function getDiagnostics(
|
||||
diagnosticsByFile: Record<string, Diagnostic[]>,
|
||||
): string[] {
|
||||
// Return a flat array of error diagnostics, in the format:
|
||||
// "ERROR [65:20] Property 'x' does not exist on type 'Y'"
|
||||
const result: string[] = []
|
||||
function formatErrorString(error: string): JSX.Element {
|
||||
const errorMarker = "Error: "
|
||||
const startsWithError = error.startsWith(errorMarker)
|
||||
|
||||
if (diagnosticsByFile === undefined) return result
|
||||
return startsWithError ? (
|
||||
<p>
|
||||
<span data-color="red" data-marker="label" data-separator>
|
||||
Error
|
||||
</span>
|
||||
<span>{error.slice(errorMarker.length)}</span>
|
||||
</p>
|
||||
) : (
|
||||
<p><span data-color="dimmed">{error}</span></p>
|
||||
)
|
||||
}
|
||||
|
||||
function getDiagnostics(
|
||||
diagnosticsByFile: Record<string, Diagnostic[]>,
|
||||
currentFile: string
|
||||
): JSX.Element[] {
|
||||
// Return a flat array of error diagnostics, in the format:
|
||||
// "Error [65:20] Property 'x' does not exist on type 'Y'"
|
||||
const result: JSX.Element[] = []
|
||||
|
||||
if (
|
||||
diagnosticsByFile === undefined || diagnosticsByFile[currentFile] === undefined
|
||||
) return result
|
||||
|
||||
for (const diags of Object.values(diagnosticsByFile)) {
|
||||
for (const d of diags) {
|
||||
@@ -189,7 +208,15 @@ export function getDiagnostics(
|
||||
const line = d.range.start.line + 1 // 1-based
|
||||
const column = d.range.start.character + 1 // 1-based
|
||||
|
||||
result.push(`ERROR [${line}:${column}] ${d.message}`)
|
||||
result.push(
|
||||
<p>
|
||||
<span data-color="red" data-marker="label">Error</span>
|
||||
<span data-color="dimmed" data-separator>
|
||||
[{line}:{column}]
|
||||
</span>
|
||||
<span>{d.message}</span>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,6 +348,57 @@ function TextPart(props: TextPartProps) {
|
||||
)
|
||||
}
|
||||
|
||||
interface ErrorPartProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
||||
expand?: boolean
|
||||
}
|
||||
function ErrorPart(props: ErrorPartProps) {
|
||||
const [local, rest] = splitProps(props, ["expand", "children"])
|
||||
const [expanded, setExpanded] = createSignal(false)
|
||||
const [overflowed, setOverflowed] = createSignal(false)
|
||||
let preEl: HTMLElement | undefined
|
||||
|
||||
function checkOverflow() {
|
||||
if (preEl && !local.expand) {
|
||||
setOverflowed(preEl.scrollHeight > preEl.clientHeight + 1)
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
checkOverflow()
|
||||
window.addEventListener("resize", checkOverflow)
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
local.children
|
||||
setTimeout(checkOverflow, 0)
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
window.removeEventListener("resize", checkOverflow)
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
class={styles["message-error"]}
|
||||
data-expanded={expanded() || local.expand === true}
|
||||
{...rest}
|
||||
>
|
||||
<div data-section="content" ref={(el) => (preEl = el)}>
|
||||
{local.children}
|
||||
</div>
|
||||
{((!local.expand && overflowed()) || expanded()) && (
|
||||
<button
|
||||
type="button"
|
||||
data-element-button-text
|
||||
onClick={() => setExpanded((e) => !e)}
|
||||
>
|
||||
{expanded() ? "Show less" : "Show more"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface MarkdownPartProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
||||
text: string
|
||||
expand?: boolean
|
||||
@@ -456,6 +534,7 @@ export default function Share(props: {
|
||||
info: SessionInfo
|
||||
messages: Record<string, SessionMessage>
|
||||
}) {
|
||||
let hasScrolled = false
|
||||
const id = props.id
|
||||
|
||||
const anchorId = createMemo<string | null>(() => {
|
||||
@@ -530,8 +609,9 @@ export default function Share(props: {
|
||||
const [, messageID] = splits
|
||||
setStore("messages", messageID, reconcile(d.content))
|
||||
|
||||
if (messageID === anchorId()) {
|
||||
if (!hasScrolled && messageID === anchorId()) {
|
||||
scrollToAnchor(window.location.hash.slice(1))
|
||||
hasScrolled = true
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -1185,12 +1265,9 @@ export default function Share(props: {
|
||||
<Switch>
|
||||
<Match when={hasError()}>
|
||||
<div data-part-tool-result>
|
||||
<TextPart
|
||||
expand
|
||||
text={toolData()?.result}
|
||||
data-size="sm"
|
||||
data-color="dimmed"
|
||||
/>
|
||||
<ErrorPart>
|
||||
{formatErrorString(toolData()?.result)}
|
||||
</ErrorPart>
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={preview()}>
|
||||
@@ -1258,7 +1335,10 @@ export default function Share(props: {
|
||||
const hasError = () => toolData()?.metadata?.error
|
||||
const content = () => toolData()?.args?.content
|
||||
const diagnostics = createMemo(() =>
|
||||
getDiagnostics(toolData()?.metadata?.diagnostics)
|
||||
getDiagnostics(
|
||||
toolData()?.metadata?.diagnostics,
|
||||
toolData()?.args.filePath
|
||||
)
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -1280,20 +1360,14 @@ export default function Share(props: {
|
||||
<b>{filePath()}</b>
|
||||
</div>
|
||||
<Show when={diagnostics().length > 0}>
|
||||
<TextPart
|
||||
data-size="sm"
|
||||
text={diagnostics().join("\n\n")}
|
||||
/>
|
||||
<ErrorPart>{diagnostics()}</ErrorPart>
|
||||
</Show>
|
||||
<Switch>
|
||||
<Match when={hasError()}>
|
||||
<div data-part-tool-result>
|
||||
<TextPart
|
||||
expand
|
||||
text={toolData()?.result}
|
||||
data-size="sm"
|
||||
data-color="dimmed"
|
||||
/>
|
||||
<ErrorPart>
|
||||
{formatErrorString(toolData()?.result)}
|
||||
</ErrorPart>
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={content()}>
|
||||
@@ -1344,7 +1418,10 @@ export default function Share(props: {
|
||||
)
|
||||
)
|
||||
const diagnostics = createMemo(() =>
|
||||
getDiagnostics(toolData()?.metadata?.diagnostics)
|
||||
getDiagnostics(
|
||||
toolData()?.metadata?.diagnostics,
|
||||
toolData()?.args.filePath
|
||||
)
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -1368,12 +1445,9 @@ export default function Share(props: {
|
||||
<Switch>
|
||||
<Match when={hasError()}>
|
||||
<div data-part-tool-result>
|
||||
<TextPart
|
||||
expand
|
||||
data-size="sm"
|
||||
data-color="dimmed"
|
||||
text={message()}
|
||||
/>
|
||||
<ErrorPart>
|
||||
{formatErrorString(message())}
|
||||
</ErrorPart>
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={diff()}>
|
||||
@@ -1387,10 +1461,7 @@ export default function Share(props: {
|
||||
</Match>
|
||||
</Switch>
|
||||
<Show when={diagnostics().length > 0}>
|
||||
<TextPart
|
||||
data-size="sm"
|
||||
text={diagnostics().join("\n\n")}
|
||||
/>
|
||||
<ErrorPart>{diagnostics()}</ErrorPart>
|
||||
</Show>
|
||||
</div>
|
||||
<ToolFooter time={toolData()?.duration || 0} />
|
||||
@@ -1541,12 +1612,9 @@ export default function Share(props: {
|
||||
<Switch>
|
||||
<Match when={hasError()}>
|
||||
<div data-part-tool-result>
|
||||
<TextPart
|
||||
expand
|
||||
text={toolData()?.result}
|
||||
data-size="sm"
|
||||
data-color="dimmed"
|
||||
/>
|
||||
<ErrorPart>
|
||||
{formatErrorString(toolData()?.result)}
|
||||
</ErrorPart>
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={toolData()?.result}>
|
||||
|
||||
@@ -421,6 +421,73 @@
|
||||
}
|
||||
}
|
||||
|
||||
.message-error {
|
||||
background-color: var(--sl-color-bg-surface);
|
||||
padding: 0.5rem calc(0.5rem + 3px);
|
||||
border-radius: 0.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
align-self: flex-start;
|
||||
max-width: var(--md-tool-width);
|
||||
|
||||
[data-section="content"] {
|
||||
p {
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: 1.5;
|
||||
font-size: 0.75rem;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-right: 0.25rem;
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
span[data-color="red"] {
|
||||
color: var(--sl-color-red);
|
||||
}
|
||||
span[data-color="dimmed"] {
|
||||
color: var(--sl-color-text-dimmed);
|
||||
}
|
||||
span[data-marker="label"] {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
span[data-separator] {
|
||||
margin-right: 0.375rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&[data-expanded="true"] {
|
||||
[data-section="content"] {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
&[data-expanded="false"] {
|
||||
[data-section="content"] {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 7;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
flex: 0 0 auto;
|
||||
padding: 2px 0;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.message-terminal {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user