mirror of
https://github.com/sst/opencode.git
synced 2025-08-29 17:34:11 +00:00
sync
This commit is contained in:
parent
7993c2ebde
commit
a0293468de
17 changed files with 393 additions and 133 deletions
36
bun.lock
36
bun.lock
|
@ -26,7 +26,7 @@
|
||||||
},
|
},
|
||||||
"cloud/core": {
|
"cloud/core": {
|
||||||
"name": "@opencode/cloud-core",
|
"name": "@opencode/cloud-core",
|
||||||
"version": "0.5.18",
|
"version": "0.5.23",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sts": "3.782.0",
|
"@aws-sdk/client-sts": "3.782.0",
|
||||||
"drizzle-orm": "0.41.0",
|
"drizzle-orm": "0.41.0",
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
},
|
},
|
||||||
"cloud/function": {
|
"cloud/function": {
|
||||||
"name": "@opencode/cloud-function",
|
"name": "@opencode/cloud-function",
|
||||||
"version": "0.5.18",
|
"version": "0.5.23",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/anthropic": "2.0.0",
|
"@ai-sdk/anthropic": "2.0.0",
|
||||||
"@ai-sdk/openai": "2.0.2",
|
"@ai-sdk/openai": "2.0.2",
|
||||||
|
@ -60,7 +60,7 @@
|
||||||
},
|
},
|
||||||
"cloud/web": {
|
"cloud/web": {
|
||||||
"name": "@opencode/cloud-web",
|
"name": "@opencode/cloud-web",
|
||||||
"version": "0.5.18",
|
"version": "0.5.23",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "0.13.9",
|
"@kobalte/core": "0.13.9",
|
||||||
"@openauthjs/solid": "0.0.0-20250322224806",
|
"@openauthjs/solid": "0.0.0-20250322224806",
|
||||||
|
@ -79,7 +79,7 @@
|
||||||
},
|
},
|
||||||
"packages/function": {
|
"packages/function": {
|
||||||
"name": "@opencode/function",
|
"name": "@opencode/function",
|
||||||
"version": "0.5.18",
|
"version": "0.5.23",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-app": "8.0.1",
|
"@octokit/auth-app": "8.0.1",
|
||||||
"@octokit/rest": "22.0.0",
|
"@octokit/rest": "22.0.0",
|
||||||
|
@ -94,7 +94,7 @@
|
||||||
},
|
},
|
||||||
"packages/opencode": {
|
"packages/opencode": {
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"version": "0.5.18",
|
"version": "0.5.23",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
|
@ -105,8 +105,8 @@
|
||||||
"@openauthjs/openauth": "0.4.3",
|
"@openauthjs/openauth": "0.4.3",
|
||||||
"@opencode-ai/plugin": "workspace:*",
|
"@opencode-ai/plugin": "workspace:*",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@opentui/core": "0.1.9",
|
"@opentui/core": "0.1.10",
|
||||||
"@opentui/solid": "0.1.9",
|
"@opentui/solid": "0.1.10",
|
||||||
"@standard-schema/spec": "1.0.0",
|
"@standard-schema/spec": "1.0.0",
|
||||||
"@zip.js/zip.js": "2.7.62",
|
"@zip.js/zip.js": "2.7.62",
|
||||||
"ai": "catalog:",
|
"ai": "catalog:",
|
||||||
|
@ -148,7 +148,7 @@
|
||||||
},
|
},
|
||||||
"packages/plugin": {
|
"packages/plugin": {
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "0.5.18",
|
"version": "0.5.23",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
},
|
},
|
||||||
|
@ -160,7 +160,7 @@
|
||||||
},
|
},
|
||||||
"packages/sdk/js": {
|
"packages/sdk/js": {
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "0.5.18",
|
"version": "0.5.23",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@hey-api/openapi-ts": "0.81.0",
|
"@hey-api/openapi-ts": "0.81.0",
|
||||||
"@tsconfig/node22": "catalog:",
|
"@tsconfig/node22": "catalog:",
|
||||||
|
@ -169,7 +169,7 @@
|
||||||
},
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "@opencode/web",
|
"name": "@opencode/web",
|
||||||
"version": "0.5.18",
|
"version": "0.5.23",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/cloudflare": "12.6.3",
|
"@astrojs/cloudflare": "12.6.3",
|
||||||
"@astrojs/markdown-remark": "6.3.1",
|
"@astrojs/markdown-remark": "6.3.1",
|
||||||
|
@ -779,21 +779,21 @@
|
||||||
|
|
||||||
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="],
|
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="],
|
||||||
|
|
||||||
"@opentui/core": ["@opentui/core@0.1.9", "", { "dependencies": { "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.9", "@opentui/core-darwin-x64": "0.1.9", "@opentui/core-linux-arm64": "0.1.9", "@opentui/core-linux-x64": "0.1.9", "@opentui/core-win32-arm64": "0.1.9", "@opentui/core-win32-x64": "0.1.9", "bun-webgpu": "0.1.3", "planck": "^1.4.2", "three": "0.177.0" } }, "sha512-GX4PNUe07hbDXxD37kKJAf3tPyMEisVFC0XA493HRFsuOAtkspnbgQGBr/5YOn4WQsXI5U/vRrSjc8pv50xEmg=="],
|
"@opentui/core": ["@opentui/core@0.1.10", "", { "dependencies": { "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.10", "@opentui/core-darwin-x64": "0.1.10", "@opentui/core-linux-arm64": "0.1.10", "@opentui/core-linux-x64": "0.1.10", "@opentui/core-win32-arm64": "0.1.10", "@opentui/core-win32-x64": "0.1.10", "bun-webgpu": "0.1.3", "planck": "^1.4.2", "three": "0.177.0" } }, "sha512-+MrWNp0fX8VPAIMHK5tMHfal5VYrXWIM5D1PEdxEQlBFZgdUADRVxktrCYZVJtdU+WwSlqJ0bQY8QzA3aXWH0A=="],
|
||||||
|
|
||||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JsMjA1T9UAecIC/XFkxhv2jVKI0OBPIJGzqdTP+7MCWz2WBNEEkLVre3L06wxL/iDYudeM44XMZBgDUM1F05Ug=="],
|
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-O0e4gIlfsPUGUOI9eFi4mStWkiUGOChvwIcJNv8Ppt+8aPPXzEgku4ptZF9G4KURXt3pgfkvVXgde1K29EnsUg=="],
|
||||||
|
|
||||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-TmpHcNySbKEBDTCcleN/06q8QZpUhSNP5YjkSfvz/qWnEL9RrzjcfZttZ2iotDomQ7ufywMFhUtHyRaFcN5tDg=="],
|
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-PmhB0Ij5Jz+5rnVq33QCuxSaiCkGBnLH3dy5MUwp11A4VohSFR8TrQz3L5k419wZdejUgedMaT83QTWME5Fbag=="],
|
||||||
|
|
||||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-XDOBwX66jWhQVdGXu6BzNIALSpRS20119K7IO4SCK3NTPaTkKk+FX+gOwbV6noWxViHhYH2wXBRXY4b/6hmJNA=="],
|
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-2TNU9DrAW1+pnqxI+9/cvtvGyBeJcyRVuddipcRZXz2k3xmmAn8A2UJ+CPJPzxET1aDsjMWiHUnESJqgkX5rog=="],
|
||||||
|
|
||||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.9", "", { "os": "linux", "cpu": "x64" }, "sha512-eQaEfsKt2fzAX4inTVPyrOcgLkRv+c3ytSs5rRhukd3QkhuX7d3s16cq+aQ4WeAbINbohvGvx3yyneWHwtUN3w=="],
|
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.10", "", { "os": "linux", "cpu": "x64" }, "sha512-7nCN+9llnRvwaaI+XAKoT+5RUvK5dykoDtOAYNIQwWB46gPwwwCXhrua8U9V3P1dPeh2E2Pvcl5qhatuBk7Gmw=="],
|
||||||
|
|
||||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-1jnO8f1WqIDFkCVpa72KX1Sv2cCfaWsAwCB1oTs/ovREVVtqtvDAp9PuFtT7iXG8hZVPtTqsZtZy5DnQLO49/A=="],
|
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-CUGL+aPxLRYQrtrVUNBVwkMj8SzCZRaB2BVgiKDZSJODzcFewEFyKDNSmvon2TkXSrjBW8TDil+lb9E3gMiw1Q=="],
|
||||||
|
|
||||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.9", "", { "os": "win32", "cpu": "x64" }, "sha512-UqhOj2j6VYM+Gvp5raU+aK2gjes3X1cDQOmyTiwK2OS3GEZIqkmGbZ+FSdttF1J2XnQp1MyBR1Zvmok45DDPMw=="],
|
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.10", "", { "os": "win32", "cpu": "x64" }, "sha512-+zPKXAA31mNGiqw0Wv+9i/NhemnbdoyKhxpw5p7vyM5mzmQbTzPJ5gLoULUNWcCEyJLK1qfp2d1yLpFjLq9tNw=="],
|
||||||
|
|
||||||
"@opentui/solid": ["@opentui/solid@0.1.9", "", { "dependencies": { "@opentui/core": "0.1.9" }, "peerDependencies": { "solid-js": "1.9.9", "typescript": "^5" } }, "sha512-yMI8C01QbEqFAGTXuLgfW4ZJHvegarD3K/HJeAIHqZ5oyLR+AgyOKg24Ih+vRaFlDtM8BBh9z4ZrSoZ1mr+v6g=="],
|
"@opentui/solid": ["@opentui/solid@0.1.10", "", { "dependencies": { "@opentui/core": "0.1.10" }, "peerDependencies": { "solid-js": "1.9.9", "typescript": "^5" } }, "sha512-kp499X1n0whyCZeBcivzRzkClI+2m/tp/bjC9Yw3Bhr6kq59y48DR/ZFqNUFr0X7NOkYFvwo3ApLtslcXoJ6qQ=="],
|
||||||
|
|
||||||
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
||||||
|
|
||||||
|
|
|
@ -33,8 +33,8 @@
|
||||||
"@openauthjs/openauth": "0.4.3",
|
"@openauthjs/openauth": "0.4.3",
|
||||||
"@opencode-ai/plugin": "workspace:*",
|
"@opencode-ai/plugin": "workspace:*",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@opentui/solid": "0.1.9",
|
"@opentui/solid": "0.1.10",
|
||||||
"@opentui/core": "0.1.9",
|
"@opentui/core": "0.1.10",
|
||||||
"@standard-schema/spec": "1.0.0",
|
"@standard-schema/spec": "1.0.0",
|
||||||
"@zip.js/zip.js": "2.7.62",
|
"@zip.js/zip.js": "2.7.62",
|
||||||
"ai": "catalog:",
|
"ai": "catalog:",
|
||||||
|
|
15
packages/opencode/src/cli/cmd/opentui/component/border.tsx
Normal file
15
packages/opencode/src/cli/cmd/opentui/component/border.tsx
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import type { BorderCharacters } from "@opentui/core";
|
||||||
|
|
||||||
|
export const SplitBorder: BorderCharacters = {
|
||||||
|
topLeft: "",
|
||||||
|
bottomLeft: "",
|
||||||
|
vertical: "┃",
|
||||||
|
topRight: "",
|
||||||
|
bottomRight: "",
|
||||||
|
horizontal: "",
|
||||||
|
bottomT: "",
|
||||||
|
topT: "",
|
||||||
|
cross: "",
|
||||||
|
leftT: "",
|
||||||
|
rightT: "",
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { useDialog } from "../ui/dialog"
|
||||||
|
import { DialogModel } from "./dialog-model"
|
||||||
|
import { DialogSelect } from "../ui/dialog-select"
|
||||||
|
import { useRoute } from "../context/route"
|
||||||
|
import { DialogSessionList } from "./dialog-session-list"
|
||||||
|
|
||||||
|
export function DialogCommand() {
|
||||||
|
const dialog = useDialog()
|
||||||
|
const route = useRoute()
|
||||||
|
return (
|
||||||
|
<DialogSelect
|
||||||
|
title="Commands"
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
title: "Switch model",
|
||||||
|
value: "switch-model",
|
||||||
|
category: "Agent",
|
||||||
|
onSelect: () => {
|
||||||
|
dialog.replace(() => <DialogModel />)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Switch session",
|
||||||
|
value: "switch-session",
|
||||||
|
category: "Session",
|
||||||
|
onSelect: () => {
|
||||||
|
dialog.replace(() => <DialogSessionList />)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "New session",
|
||||||
|
value: "new-session",
|
||||||
|
category: "Session",
|
||||||
|
onSelect: () => {
|
||||||
|
route.navigate({
|
||||||
|
type: "home",
|
||||||
|
})
|
||||||
|
dialog.clear()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Share session",
|
||||||
|
value: "share-session",
|
||||||
|
category: "Session",
|
||||||
|
onSelect: () => {
|
||||||
|
console.log("share session")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
import { createMemo } from "solid-js";
|
import { createMemo } from "solid-js";
|
||||||
import { useLocal } from "../context/local";
|
import { useLocal } from "../context/local";
|
||||||
import { useSync } from "../context/sync";
|
import { useSync } from "../context/sync";
|
||||||
import { map, pipe, flatMap, entries, filter } from "remeda";
|
import { map, pipe, flatMap, entries, filter, isDeepEqual } from "remeda";
|
||||||
import { DialogSelect } from "./dialog-select";
|
import { DialogSelect } from "../ui/dialog-select";
|
||||||
import { useDialog } from "./dialog";
|
import { useDialog } from "../ui/dialog";
|
||||||
|
|
||||||
export function DialogModel() {
|
export function DialogModel() {
|
||||||
const local = useLocal()
|
const local = useLocal()
|
||||||
|
@ -11,14 +11,16 @@ export function DialogModel() {
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
|
|
||||||
const options = createMemo(() => [
|
const options = createMemo(() => [
|
||||||
...local.model.recent().map(key => {
|
...local.model.recent().map(item => {
|
||||||
const [providerID, ...rest] = key.split("/")
|
const provider = sync.data.provider.find((x) => x.id === item.providerID)!
|
||||||
const provider = sync.data.provider.find((x) => x.id === providerID)!
|
const model = provider.models[item.modelID]
|
||||||
const modelID = rest.join("/")
|
|
||||||
const model = provider.models[modelID]
|
|
||||||
return {
|
return {
|
||||||
key,
|
key: item,
|
||||||
title: model.name ?? modelID,
|
value: {
|
||||||
|
providerID: provider.id,
|
||||||
|
modelID: model.id,
|
||||||
|
},
|
||||||
|
title: model.name ?? item.modelID,
|
||||||
description: provider.name,
|
description: provider.name,
|
||||||
category: "Recent",
|
category: "Recent",
|
||||||
}
|
}
|
||||||
|
@ -29,12 +31,15 @@ export function DialogModel() {
|
||||||
provider.models,
|
provider.models,
|
||||||
entries(),
|
entries(),
|
||||||
map(([model, info]) => ({
|
map(([model, info]) => ({
|
||||||
key: `${provider.id}/${model}`,
|
value: {
|
||||||
|
providerID: provider.id,
|
||||||
|
modelID: model,
|
||||||
|
},
|
||||||
title: info.name ?? model,
|
title: info.name ?? model,
|
||||||
description: provider.name,
|
description: provider.name,
|
||||||
category: provider.name,
|
category: provider.name,
|
||||||
})),
|
})),
|
||||||
filter(x => !local.model.recent().includes(x.key)),
|
filter(x => !local.model.recent().find(y => isDeepEqual(y, x.value))),
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
])
|
])
|
||||||
|
@ -44,7 +49,7 @@ export function DialogModel() {
|
||||||
current={local.model.current()}
|
current={local.model.current()}
|
||||||
options={options()}
|
options={options()}
|
||||||
onSelect={option => {
|
onSelect={option => {
|
||||||
local.model.set(option.key, { recent: true })
|
local.model.set(option.value, { recent: true })
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
}} />
|
}} />
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { useDialog } from "../ui/dialog"
|
||||||
|
import { DialogModel } from "./dialog-model"
|
||||||
|
import { DialogSelect } from "../ui/dialog-select"
|
||||||
|
import { useRoute } from "../context/route"
|
||||||
|
import { useSync } from "../context/sync"
|
||||||
|
import { createMemo } from "solid-js"
|
||||||
|
|
||||||
|
export function DialogSessionList() {
|
||||||
|
const dialog = useDialog()
|
||||||
|
const sync = useSync()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const options = createMemo(() => {
|
||||||
|
const today = new Date().toDateString()
|
||||||
|
return Object.values(sync.data.session).map((x) => {
|
||||||
|
let category = new Date(x.time.created).toDateString()
|
||||||
|
if (category === today) {
|
||||||
|
category = "Today"
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
title: x.title,
|
||||||
|
value: x.id,
|
||||||
|
category,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogSelect
|
||||||
|
title="Sessions"
|
||||||
|
options={options()}
|
||||||
|
onSelect={(option) => {
|
||||||
|
route.navigate({
|
||||||
|
type: "session",
|
||||||
|
sessionID: option.value,
|
||||||
|
})
|
||||||
|
dialog.clear()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
41
packages/opencode/src/cli/cmd/opentui/component/prompt.tsx
Normal file
41
packages/opencode/src/cli/cmd/opentui/component/prompt.tsx
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { InputRenderable, TextAttributes, fg, bold } from "@opentui/core"
|
||||||
|
import { createEffect } from "solid-js"
|
||||||
|
import { useLocal } from "../context/local"
|
||||||
|
import { Theme } from "../context/theme"
|
||||||
|
import { useDialog } from "../ui/dialog"
|
||||||
|
|
||||||
|
|
||||||
|
export type PromptProps = {
|
||||||
|
onSubmit?: (value: string) => void
|
||||||
|
}
|
||||||
|
export function Prompt(props: PromptProps) {
|
||||||
|
let input: InputRenderable
|
||||||
|
const dialog = useDialog()
|
||||||
|
const local = useLocal()
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (dialog.stack.length === 0 && input)
|
||||||
|
input.focus()
|
||||||
|
if (dialog.stack.length > 0)
|
||||||
|
input.blur()
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<box>
|
||||||
|
<box flexDirection="row">
|
||||||
|
<box backgroundColor={Theme.backgroundElement} width={3} border={false} justifyContent="center" alignItems="center">
|
||||||
|
<text attributes={TextAttributes.BOLD} fg={Theme.primary}>{">"}</text>
|
||||||
|
</box>
|
||||||
|
<box border={false} paddingTop={1} paddingBottom={2} backgroundColor={Theme.backgroundElement} flexGrow={1}>
|
||||||
|
<input onSubmit={props.onSubmit} ref={r => input = r} onMouseDown={r => r.target?.focus()} focusedBackgroundColor={Theme.backgroundElement} cursorColor={Theme.primary} backgroundColor={Theme.backgroundElement} />
|
||||||
|
</box>
|
||||||
|
<box backgroundColor={Theme.backgroundElement} width={1} border={false} justifyContent="center" alignItems="center">
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
<group paddingLeft={2} paddingRight={1} flexDirection="row" justifyContent="space-between">
|
||||||
|
<text>enter {fg(Theme.textMuted)("send")}</text>
|
||||||
|
<text>{fg(Theme.textMuted)(local.model.parsed().provider)} {bold(local.model.parsed().model)}</text>
|
||||||
|
</group >
|
||||||
|
</box>
|
||||||
|
)
|
||||||
|
}
|
|
@ -2,9 +2,10 @@ import { createStore } from "solid-js/store"
|
||||||
import { batch, createContext, createEffect, createMemo, useContext, type ParentProps } from "solid-js"
|
import { batch, createContext, createEffect, createMemo, useContext, type ParentProps } from "solid-js"
|
||||||
import { useSync } from "./sync"
|
import { useSync } from "./sync"
|
||||||
import { Theme } from "./theme"
|
import { Theme } from "./theme"
|
||||||
import { unique } from "remeda"
|
import { unique, uniqueBy } from "remeda"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { Global } from "../../../../global"
|
import { Global } from "../../../../global"
|
||||||
|
import type { Agent } from "@opencode-ai/sdk"
|
||||||
|
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
|
@ -29,7 +30,10 @@ function init() {
|
||||||
const value = agents()[next]
|
const value = agents()[next]
|
||||||
setStore("current", value.name)
|
setStore("current", value.name)
|
||||||
if (value.model)
|
if (value.model)
|
||||||
model.set(`${value.model.providerID}/${value.model.modelID}`)
|
model.set({
|
||||||
|
providerID: value.model.providerID,
|
||||||
|
modelID: value.model.modelID,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
color(name: string) {
|
color(name: string) {
|
||||||
const index = agents().findIndex((x) => x.name === name)
|
const index = agents().findIndex((x) => x.name === name)
|
||||||
|
@ -41,8 +45,14 @@ function init() {
|
||||||
|
|
||||||
const model = (() => {
|
const model = (() => {
|
||||||
const [store, setStore] = createStore<{
|
const [store, setStore] = createStore<{
|
||||||
model: Record<string, string>
|
model: Record<string, {
|
||||||
recent: string[]
|
providerID: string
|
||||||
|
modelID: string
|
||||||
|
}>
|
||||||
|
recent: {
|
||||||
|
providerID: string
|
||||||
|
modelID: string
|
||||||
|
}[]
|
||||||
}>({
|
}>({
|
||||||
model: {},
|
model: {},
|
||||||
recent: []
|
recent: []
|
||||||
|
@ -63,12 +73,15 @@ function init() {
|
||||||
const fallback = createMemo(() => {
|
const fallback = createMemo(() => {
|
||||||
const provider = sync.data.provider[0]
|
const provider = sync.data.provider[0]
|
||||||
const model = Object.values(provider.models)[0]
|
const model = Object.values(provider.models)[0]
|
||||||
return `${provider.id}/${model.id}`
|
return {
|
||||||
|
providerID: provider.id,
|
||||||
|
modelID: model.id,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const current = createMemo<string>(() => {
|
const current = createMemo(() => {
|
||||||
const a = agent.current()
|
const a = agent.current()
|
||||||
return store.model[agent.current().name] ?? (a.model ? `${a.model.providerID}/${a.model.modelID}` : fallback())
|
return store.model[agent.current().name] ?? (a.model ? a.model : fallback())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -79,20 +92,18 @@ function init() {
|
||||||
},
|
},
|
||||||
parsed: createMemo(() => {
|
parsed: createMemo(() => {
|
||||||
const value = current()
|
const value = current()
|
||||||
const [providerID, ...rest] = value.split("/")
|
const provider = sync.data.provider.find((x) => x.id === value.providerID)!
|
||||||
const provider = sync.data.provider.find((x) => x.id === providerID)!
|
const model = provider.models[value.modelID]
|
||||||
const modelID = rest.join("/")
|
|
||||||
const model = provider.models[modelID]
|
|
||||||
return {
|
return {
|
||||||
provider: provider.name ?? providerID,
|
provider: provider.name ?? value.providerID,
|
||||||
model: model.name ?? modelID,
|
model: model.name ?? value.modelID,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
set(model: string, options?: { recent?: boolean }) {
|
set(model: { providerID: string, modelID: string }, options?: { recent?: boolean }) {
|
||||||
batch(() => {
|
batch(() => {
|
||||||
setStore("model", agent.current().name, model)
|
setStore("model", agent.current().name, model)
|
||||||
if (options?.recent) {
|
if (options?.recent) {
|
||||||
const uniq = unique([model, ...store.recent])
|
const uniq = uniqueBy([model, ...store.recent], (x) => x.providerID + x.modelID)
|
||||||
if (uniq.length > 5) uniq.pop()
|
if (uniq.length > 5) uniq.pop()
|
||||||
setStore("recent", uniq)
|
setStore("recent", uniq)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,15 +11,16 @@ type Route =
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
const [store, setStore] = createStore<Route>({
|
const [store, setStore] = createStore<Route>(
|
||||||
type: "home",
|
{ type: 'session', sessionID: 'ses_71b466c91ffelM4E0ltHr2sQr3' }
|
||||||
})
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
get route() {
|
get data() {
|
||||||
return store
|
return store
|
||||||
},
|
},
|
||||||
set navigate(route: Route) {
|
navigate(route: Route) {
|
||||||
|
console.log("navigate", route)
|
||||||
setStore(route)
|
setStore(route)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -42,3 +43,8 @@ export function useRoute() {
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useRouteData<T extends Route["type"]>(type: T) {
|
||||||
|
const route = useRoute()
|
||||||
|
return route.data as Extract<Route, { type: typeof type }>
|
||||||
|
}
|
||||||
|
|
|
@ -4,10 +4,12 @@ import { Server } from "../../../../server/server"
|
||||||
|
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
const app = Server.app()
|
const server = Server.listen({
|
||||||
|
port: 0,
|
||||||
|
hostname: "127.0.0.1",
|
||||||
|
})
|
||||||
const client = createOpencodeClient({
|
const client = createOpencodeClient({
|
||||||
baseUrl: "http://localhost:4096",
|
baseUrl: server.url.toString(),
|
||||||
fetch: async (...args) => app.fetch(...args),
|
|
||||||
})
|
})
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import type { Agent, Provider, Session } from "@opencode-ai/sdk"
|
import type { Message, Agent, Provider, Session, Part } from "@opencode-ai/sdk"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore, produce } from "solid-js/store"
|
||||||
import { useSDK } from "./sdk"
|
import { useSDK } from "./sdk"
|
||||||
import { createContext, onMount, Show, useContext, type ParentProps } from "solid-js"
|
import { createContext, Show, useContext, type ParentProps } from "solid-js"
|
||||||
import type { Message } from "vscode-jsonrpc"
|
|
||||||
|
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
|
@ -10,27 +9,51 @@ function init() {
|
||||||
ready: boolean
|
ready: boolean
|
||||||
provider: Provider[]
|
provider: Provider[]
|
||||||
agent: Agent[]
|
agent: Agent[]
|
||||||
session: Record<string, {
|
session: {
|
||||||
info: Session
|
[sessionID: string]: Session
|
||||||
message: Record<string, {
|
}
|
||||||
info: Message
|
message: {
|
||||||
part: Record<string, Message>
|
[sessionID: string]: {
|
||||||
}>
|
[messageID: string]: Message
|
||||||
}>
|
}
|
||||||
|
}
|
||||||
|
part: {
|
||||||
|
[sessionID: string]: {
|
||||||
|
[messageID: string]: {
|
||||||
|
[partID: string]: Part
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}>({
|
}>({
|
||||||
ready: false,
|
ready: false,
|
||||||
agent: [],
|
agent: [],
|
||||||
provider: [],
|
provider: [],
|
||||||
session: {},
|
session: {},
|
||||||
|
message: {},
|
||||||
|
part: {},
|
||||||
})
|
})
|
||||||
|
|
||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
|
|
||||||
onMount(async () => {
|
sdk.event.subscribe().then(async events => {
|
||||||
const events = await sdk.event.subscribe()
|
|
||||||
for await (const event of events.stream) {
|
for await (const event of events.stream) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case "storage.write":
|
case "session.updated":
|
||||||
|
setStore("session", event.properties.info.id, event.properties.info)
|
||||||
|
break
|
||||||
|
case "message.updated":
|
||||||
|
setStore("message", produce((message) => {
|
||||||
|
message[event.properties.info.sessionID] ??= {}
|
||||||
|
message[event.properties.info.sessionID][event.properties.info.id] = event.properties.info
|
||||||
|
}))
|
||||||
|
break
|
||||||
|
|
||||||
|
case "message.part.updated":
|
||||||
|
setStore("part", produce((part) => {
|
||||||
|
part[event.properties.part.sessionID] ??= {}
|
||||||
|
part[event.properties.part.sessionID][event.properties.part.messageID] ??= {}
|
||||||
|
part[event.properties.part.sessionID][event.properties.part.messageID][event.properties.part.id] = event.properties.part
|
||||||
|
}))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,6 +62,12 @@ function init() {
|
||||||
Promise.all([
|
Promise.all([
|
||||||
sdk.config.providers().then((x) => setStore("provider", x.data!.providers)),
|
sdk.config.providers().then((x) => setStore("provider", x.data!.providers)),
|
||||||
sdk.app.agents().then((x) => setStore("agent", x.data ?? [])),
|
sdk.app.agents().then((x) => setStore("agent", x.data ?? [])),
|
||||||
|
sdk.session.list().then((x) =>
|
||||||
|
setStore("session", x.data!.reduce((acc, item) => {
|
||||||
|
acc[item.id] = item
|
||||||
|
return acc
|
||||||
|
}, {} as Record<string, Session>))
|
||||||
|
),
|
||||||
]).then(() => setStore("ready", true))
|
]).then(() => setStore("ready", true))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
import { createEffect } from "solid-js";
|
|
||||||
import { Installation } from "../../../installation";
|
import { Installation } from "../../../installation";
|
||||||
import { Theme } from "./context/theme";
|
import { Theme } from "./context/theme";
|
||||||
import { InputRenderable, TextAttributes, bold, fg } from "@opentui/core"
|
import { TextAttributes, bold, fg } from "@opentui/core"
|
||||||
import { useDialog } from "./ui/dialog";
|
import { Prompt } from "./component/prompt";
|
||||||
|
import { useSDK } from "./context/sdk";
|
||||||
|
import { useRoute } from "./context/route";
|
||||||
import { useLocal } from "./context/local";
|
import { useLocal } from "./context/local";
|
||||||
|
|
||||||
export function Home() {
|
export function Home() {
|
||||||
|
const sdk = useSDK()
|
||||||
|
const route = useRoute()
|
||||||
|
const local = useLocal()
|
||||||
return (
|
return (
|
||||||
<group flexGrow={1} justifyContent="center" alignItems="center">
|
<group flexGrow={1} justifyContent="center" alignItems="center">
|
||||||
<group>
|
<group>
|
||||||
|
@ -19,49 +23,31 @@ export function Home() {
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
<group paddingTop={3} >
|
<group paddingTop={3} >
|
||||||
<Prompt />
|
<Prompt onSubmit={async (val) => {
|
||||||
</group >
|
const session = await sdk.session.create({
|
||||||
</group>
|
body: {
|
||||||
)
|
},
|
||||||
}
|
|
||||||
|
|
||||||
function Prompt() {
|
|
||||||
let input: InputRenderable
|
|
||||||
const dialog = useDialog()
|
|
||||||
const local = useLocal()
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (dialog.stack.length === 0 && input)
|
|
||||||
input.focus()
|
|
||||||
if (dialog.stack.length > 0)
|
|
||||||
input.blur()
|
|
||||||
})
|
})
|
||||||
|
route.navigate({
|
||||||
return (
|
type: "session",
|
||||||
<group>
|
sessionID: session.data!.id,
|
||||||
<group flexDirection="row">
|
})
|
||||||
<group>
|
await sdk.session.chat({
|
||||||
<text fg={Theme.textMuted}>┃</text>
|
path: {
|
||||||
<text fg={Theme.textMuted}>┃</text>
|
id: session.data!.id,
|
||||||
<text fg={Theme.textMuted}>┃</text>
|
},
|
||||||
</group>
|
body: {
|
||||||
<box backgroundColor={Theme.backgroundElement} width={3} border={false} justifyContent="center" alignItems="center">
|
...local.model.current(),
|
||||||
<text attributes={TextAttributes.BOLD} fg={Theme.primary}>{">"}</text>
|
agent: local.agent.current().name,
|
||||||
</box>
|
parts: [
|
||||||
<box border={false} paddingTop={1} paddingBottom={2} backgroundColor={Theme.backgroundElement}>
|
{
|
||||||
<input ref={r => input = r} onMouseDown={r => r.target?.focus()} focusedBackgroundColor={Theme.backgroundElement} cursorColor={Theme.primary} backgroundColor={Theme.backgroundElement} width={70} />
|
type: "text",
|
||||||
</box>
|
text: val,
|
||||||
<box backgroundColor={Theme.backgroundElement} width={1} border={false} justifyContent="center" alignItems="center">
|
}
|
||||||
</box>
|
]
|
||||||
<group>
|
},
|
||||||
<text fg={Theme.textMuted}>┃</text>
|
})
|
||||||
<text fg={Theme.textMuted}>┃</text>
|
}} />
|
||||||
<text fg={Theme.textMuted}>┃</text>
|
|
||||||
</group>
|
|
||||||
</group>
|
|
||||||
<group paddingLeft={2} paddingRight={1} flexDirection="row" justifyContent="space-between">
|
|
||||||
<text>enter {fg(Theme.textMuted)("send")}</text>
|
|
||||||
<text>{fg(Theme.textMuted)(local.model.parsed().provider)} {bold(local.model.parsed().model)}</text>
|
|
||||||
</group >
|
</group >
|
||||||
</group>
|
</group>
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,7 +12,9 @@ import { bootstrap } from "../../bootstrap"
|
||||||
import { SDKProvider } from "./context/sdk"
|
import { SDKProvider } from "./context/sdk"
|
||||||
import { SyncProvider } from "./context/sync"
|
import { SyncProvider } from "./context/sync"
|
||||||
import { LocalProvider, useLocal } from "./context/local"
|
import { LocalProvider, useLocal } from "./context/local"
|
||||||
import { DialogModel } from "./ui/dialog-model"
|
import { DialogModel } from "./component/dialog-model"
|
||||||
|
import { DialogCommand } from "./component/dialog-command"
|
||||||
|
import { Session } from "./session"
|
||||||
|
|
||||||
export const OpentuiCommand = cmd({
|
export const OpentuiCommand = cmd({
|
||||||
command: "opentui",
|
command: "opentui",
|
||||||
|
@ -53,6 +55,11 @@ function App() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (evt.ctrl && evt.name === "p") {
|
||||||
|
dialog.replace(() => <DialogCommand />)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (evt.meta && evt.name === "d") {
|
if (evt.meta && evt.name === "d") {
|
||||||
renderer.console.toggle()
|
renderer.console.toggle()
|
||||||
return
|
return
|
||||||
|
@ -67,9 +74,12 @@ function App() {
|
||||||
<box border={false} width={dimensions().width} height={dimensions().height} backgroundColor={Theme.background}>
|
<box border={false} width={dimensions().width} height={dimensions().height} backgroundColor={Theme.background}>
|
||||||
<group flexDirection="column" flexGrow={1}>
|
<group flexDirection="column" flexGrow={1}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={route.route.type === "home"}>
|
<Match when={route.data.type === "home"}>
|
||||||
<Home />
|
<Home />
|
||||||
</Match>
|
</Match>
|
||||||
|
<Match when={route.data.type === "session"}>
|
||||||
|
<Session />
|
||||||
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
</group>
|
</group>
|
||||||
<box border={false} height={1} backgroundColor={Theme.backgroundPanel} flexDirection="row" justifyContent="space-between">
|
<box border={false} height={1} backgroundColor={Theme.backgroundPanel} flexDirection="row" justifyContent="space-between">
|
||||||
|
|
40
packages/opencode/src/cli/cmd/opentui/session.tsx
Normal file
40
packages/opencode/src/cli/cmd/opentui/session.tsx
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { createEffect, createMemo, Match, Show, Switch } from "solid-js";
|
||||||
|
import { useRoute, useRouteData } from "./context/route";
|
||||||
|
import { useSync } from "./context/sync";
|
||||||
|
import { SplitBorder } from "./component/border";
|
||||||
|
import { Theme } from "./context/theme";
|
||||||
|
import { bold, fg } from "@opentui/core";
|
||||||
|
import { Prompt } from "./component/prompt";
|
||||||
|
import { useTerminalDimensions } from "@opentui/solid";
|
||||||
|
|
||||||
|
export function Session() {
|
||||||
|
const route = useRouteData("session")
|
||||||
|
const sync = useSync()
|
||||||
|
const session = createMemo(() => sync.data.session[route.sessionID])
|
||||||
|
const dimensions = useTerminalDimensions()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Show when={session()}>
|
||||||
|
<box paddingTop={1} paddingBottom={1} paddingLeft={2} paddingRight={2} flexGrow={1}>
|
||||||
|
<box customBorderChars={SplitBorder} border={["left", "right"]} borderColor={Theme.backgroundElement} paddingLeft={1} paddingRight={1}>
|
||||||
|
<text>{bold(fg(Theme.accent)("#"))} {bold(session().title)}</text>
|
||||||
|
<group flexDirection="row">
|
||||||
|
<Switch>
|
||||||
|
<Match when={session().share?.url}>
|
||||||
|
<text fg={Theme.textMuted}>{session().share!.url}</text>
|
||||||
|
</Match>
|
||||||
|
<Match when={true}>
|
||||||
|
<text>/share {fg(Theme.textMuted)("to create a shareable link")}</text>
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
</group>
|
||||||
|
</box>
|
||||||
|
<box flexGrow={1}>
|
||||||
|
</box>
|
||||||
|
<group>
|
||||||
|
<Prompt />
|
||||||
|
</group>
|
||||||
|
</box >
|
||||||
|
</Show >
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,26 +1,28 @@
|
||||||
import { InputRenderable, RGBA, TextAttributes } from "@opentui/core"
|
import { InputRenderable, RGBA, TextAttributes } from "@opentui/core"
|
||||||
import { Theme } from "../context/theme"
|
import { Theme } from "../context/theme"
|
||||||
import { entries, flatMap, groupBy, mapValues, pipe, take } from "remeda"
|
import { entries, flatMap, groupBy, pipe, take } from "remeda"
|
||||||
import { createEffect, createMemo, For, Show } from "solid-js"
|
import { createEffect, createMemo, For, Show } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { useKeyHandler } from "@opentui/solid"
|
import { useKeyHandler } from "@opentui/solid"
|
||||||
import * as fuzzysort from "fuzzysort"
|
import * as fuzzysort from "fuzzysort"
|
||||||
|
import { isDeepEqual } from "remeda"
|
||||||
|
|
||||||
export interface DialogSelectProps {
|
export interface DialogSelectProps<T> {
|
||||||
title: string
|
title: string
|
||||||
options: DialogSelectOption[]
|
options: DialogSelectOption<T>[]
|
||||||
onSelect: (option: DialogSelectOption) => void
|
onSelect?: (option: DialogSelectOption<T>) => void
|
||||||
current?: string
|
current?: T
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DialogSelectOption {
|
export interface DialogSelectOption<T> {
|
||||||
key: string
|
value: T
|
||||||
title: string
|
title: string
|
||||||
description?: string
|
description?: string
|
||||||
category?: string
|
category?: string
|
||||||
|
onSelect?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DialogSelect(props: DialogSelectProps) {
|
export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
selected: 0,
|
selected: 0,
|
||||||
filter: ""
|
filter: ""
|
||||||
|
@ -64,14 +66,18 @@ export function DialogSelect(props: DialogSelectProps) {
|
||||||
useKeyHandler((evt) => {
|
useKeyHandler((evt) => {
|
||||||
if (evt.name === "up") move(-1)
|
if (evt.name === "up") move(-1)
|
||||||
if (evt.name === "down") move(1)
|
if (evt.name === "down") move(1)
|
||||||
if (evt.name === "return") props.onSelect(flat()[store.selected])
|
if (evt.name === "return") {
|
||||||
|
const option = flat()[store.selected]
|
||||||
|
if (option.onSelect) option.onSelect()
|
||||||
|
props.onSelect?.(option)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group>
|
<group>
|
||||||
<group paddingLeft={2} paddingRight={2}>
|
<group paddingLeft={2} paddingRight={2}>
|
||||||
<group paddingLeft={1} paddingRight={1}>
|
<group paddingLeft={1}>
|
||||||
<group flexDirection="row" justifyContent="space-between">
|
<group flexDirection="row" justifyContent="space-between">
|
||||||
<text attributes={TextAttributes.BOLD}>{props.title}</text>
|
<text attributes={TextAttributes.BOLD}>{props.title}</text>
|
||||||
<text fg={Theme.textMuted}>esc</text>
|
<text fg={Theme.textMuted}>esc</text>
|
||||||
|
@ -91,16 +97,19 @@ export function DialogSelect(props: DialogSelectProps) {
|
||||||
<group paddingBottom={1} >
|
<group paddingBottom={1} >
|
||||||
<For each={grouped()}>
|
<For each={grouped()}>
|
||||||
{([category, options]) =>
|
{([category, options]) =>
|
||||||
<group paddingTop={1} flexShrink={0} >
|
<group flexShrink={0} >
|
||||||
<Show when={category}>
|
<Show when={category}>
|
||||||
<group paddingLeft={1} >
|
<group paddingTop={1} paddingLeft={1} >
|
||||||
<text fg={Theme.accent} attributes={TextAttributes.BOLD}>{category}</text>
|
<text fg={Theme.accent} attributes={TextAttributes.BOLD}>{category}</text>
|
||||||
</group>
|
</group>
|
||||||
</Show>
|
</Show>
|
||||||
<For each={options}>
|
<For each={options}>
|
||||||
{(option) =>
|
{(option) =>
|
||||||
<Option
|
<Option
|
||||||
title={option.title} description={option.description !== category ? option.description : undefined} active={option.key === flat()[store.selected].key} current={option.key === props.current} />
|
title={option.title}
|
||||||
|
description={option.description !== category ? option.description : undefined}
|
||||||
|
active={isDeepEqual(option.value, flat()[store.selected].value)}
|
||||||
|
current={isDeepEqual(option.value, props.current)} />
|
||||||
}
|
}
|
||||||
</For>
|
</For>
|
||||||
</group>
|
</group>
|
||||||
|
@ -108,7 +117,7 @@ export function DialogSelect(props: DialogSelectProps) {
|
||||||
</For>
|
</For>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
<box border={false} paddingRight={2} paddingLeft={3} paddingBottom={1} paddingTop={1} flexDirection="row" >
|
<box border={false} paddingRight={2} paddingLeft={3} paddingBottom={1} flexDirection="row" >
|
||||||
<text fg={Theme.text} attributes={TextAttributes.BOLD}>n</text>
|
<text fg={Theme.text} attributes={TextAttributes.BOLD}>n</text>
|
||||||
<text fg={Theme.textMuted}> new</text>
|
<text fg={Theme.textMuted}> new</text>
|
||||||
<text fg={Theme.text} attributes={TextAttributes.BOLD}>{" "}r</text>
|
<text fg={Theme.text} attributes={TextAttributes.BOLD}>{" "}r</text>
|
||||||
|
|
|
@ -35,7 +35,7 @@ export function Dialog(props: ParentProps) {
|
||||||
<box
|
<box
|
||||||
border={false}
|
border={false}
|
||||||
customBorderChars={Border}
|
customBorderChars={Border}
|
||||||
width={76}
|
width={60}
|
||||||
maxWidth={dimensions().width - 2}
|
maxWidth={dimensions().width - 2}
|
||||||
backgroundColor={Theme.backgroundPanel}
|
backgroundColor={Theme.backgroundPanel}
|
||||||
borderColor={Theme.border}
|
borderColor={Theme.border}
|
||||||
|
|
11
packages/sdk/js/example/test.ts
Normal file
11
packages/sdk/js/example/test.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { createOpencodeClient, createOpencodeServer } from "../src/index"
|
||||||
|
|
||||||
|
const client = createOpencodeClient({
|
||||||
|
baseUrl: "http://localhost:4096",
|
||||||
|
})
|
||||||
|
|
||||||
|
await client.event.subscribe().then(async (event) => {
|
||||||
|
for await (const e of event.stream) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
})
|
Loading…
Add table
Add a link
Reference in a new issue