Merge remote-tracking branch 'origin/dev' into fix/google-vertex-anthropic-thinking
commit
87f0b68f5a
41
bun.lock
41
bun.lock
|
|
@ -23,7 +23,7 @@
|
|||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
|
|
@ -73,7 +73,7 @@
|
|||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
|
|
@ -107,7 +107,7 @@
|
|||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
|
|
@ -134,7 +134,7 @@
|
|||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "2.0.0",
|
||||
"@ai-sdk/openai": "2.0.2",
|
||||
|
|
@ -158,7 +158,7 @@
|
|||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
|
@ -182,7 +182,7 @@
|
|||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"dependencies": {
|
||||
"@opencode-ai/app": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
|
|
@ -211,7 +211,7 @@
|
|||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"dependencies": {
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
|
|
@ -240,7 +240,7 @@
|
|||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
|
|
@ -256,7 +256,7 @@
|
|||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
|
|
@ -360,7 +360,7 @@
|
|||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"zod": "catalog:",
|
||||
|
|
@ -380,9 +380,9 @@
|
|||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "0.90.4",
|
||||
"@hey-api/openapi-ts": "0.90.10",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
|
|
@ -391,7 +391,7 @@
|
|||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
|
|
@ -404,7 +404,7 @@
|
|||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
|
|
@ -423,6 +423,7 @@
|
|||
"marked": "catalog:",
|
||||
"marked-katex-extension": "5.1.6",
|
||||
"marked-shiki": "catalog:",
|
||||
"morphdom": "2.7.8",
|
||||
"remeda": "catalog:",
|
||||
"shiki": "catalog:",
|
||||
"solid-js": "catalog:",
|
||||
|
|
@ -445,7 +446,7 @@
|
|||
},
|
||||
"packages/util": {
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"dependencies": {
|
||||
"zod": "catalog:",
|
||||
},
|
||||
|
|
@ -456,7 +457,7 @@
|
|||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
|
|
@ -928,11 +929,13 @@
|
|||
|
||||
"@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.0.11", "", { "dependencies": { "@types/node": "^20.0.0", "happy-dom": "^20.0.11" } }, "sha512-GqNqiShBT/lzkHTMC/slKBrvN0DsD4Di8ssBk4aDaVgEn+2WMzE6DXxq701ndSXj7/0cJ8mNT71pM7Bnrr6JRw=="],
|
||||
|
||||
"@hey-api/codegen-core": ["@hey-api/codegen-core@0.5.2", "", { "dependencies": { "ansi-colors": "4.1.3", "color-support": "1.1.3" }, "peerDependencies": { "typescript": ">=5.5.3" } }, "sha512-88cqrrB2cLXN8nMOHidQTcVOnZsJ5kebEbBefjMCifaUCwTA30ouSSWvTZqrOX4O104zjJyu7M8Gcv/NNYQuaA=="],
|
||||
"@hey-api/codegen-core": ["@hey-api/codegen-core@0.5.5", "", { "dependencies": { "@hey-api/types": "0.1.2", "ansi-colors": "4.1.3", "c12": "3.3.3", "color-support": "1.1.3" }, "peerDependencies": { "typescript": ">=5.5.3" } }, "sha512-f2ZHucnA2wBGAY8ipB4wn/mrEYW+WUxU2huJmUvfDO6AE2vfILSHeF3wCO39Pz4wUYPoAWZByaauftLrOfC12Q=="],
|
||||
|
||||
"@hey-api/json-schema-ref-parser": ["@hey-api/json-schema-ref-parser@1.2.2", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.1", "lodash": "^4.17.21" } }, "sha512-oS+5yAdwnK20lSeFO1d53Ku+yaGCsY8PcrmSq2GtSs3bsBfRnHAbpPKSVzQcaxAOrzj5NB+f34WhZglVrNayBA=="],
|
||||
|
||||
"@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.90.4", "", { "dependencies": { "@hey-api/codegen-core": "^0.5.2", "@hey-api/json-schema-ref-parser": "1.2.2", "ansi-colors": "4.1.3", "c12": "3.3.3", "color-support": "1.1.3", "commander": "14.0.2", "open": "11.0.0", "semver": "7.7.3" }, "peerDependencies": { "typescript": ">=5.5.3" }, "bin": { "openapi-ts": "bin/run.js" } }, "sha512-9l++kjcb0ui4JqPlueZ6OZ9zKn6eK/8//Z2jHcIXb5MRwDRgubOOSpTU5llEv3uvWfT10VzcMp99dySWq0AASw=="],
|
||||
"@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.90.10", "", { "dependencies": { "@hey-api/codegen-core": "^0.5.5", "@hey-api/json-schema-ref-parser": "1.2.2", "@hey-api/types": "0.1.2", "ansi-colors": "4.1.3", "color-support": "1.1.3", "commander": "14.0.2", "open": "11.0.0", "semver": "7.7.3" }, "peerDependencies": { "typescript": ">=5.5.3" }, "bin": { "openapi-ts": "bin/run.js" } }, "sha512-o0wlFxuLt1bcyIV/ZH8DQ1wrgODTnUYj/VfCHOOYgXUQlLp9Dm2PjihOz+WYrZLowhqUhSKeJRArOGzvLuOTsg=="],
|
||||
|
||||
"@hey-api/types": ["@hey-api/types@0.1.2", "", {}, "sha512-uNNtiVAWL7XNrV/tFXx7GLY9lwaaDazx1173cGW3+UEaw4RUPsHEmiB4DSpcjNxMIcrctfz2sGKLnVx5PBG2RA=="],
|
||||
|
||||
"@hono/node-server": ["@hono/node-server@1.19.7", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw=="],
|
||||
|
||||
|
|
@ -3102,6 +3105,8 @@
|
|||
|
||||
"mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="],
|
||||
|
||||
"morphdom": ["morphdom@2.7.8", "", {}, "sha512-D/fR4xgGUyVRbdMGU6Nejea1RFzYxYtyurG4Fbv2Fi/daKlWKuXGLOdXtl+3eIwL110cI2hz1ZojGICjjFLgTg=="],
|
||||
|
||||
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-spng4G0hikHyNhplZiXL/K4XzSBsQNwFCrtzatuY+e4=",
|
||||
"aarch64-linux": "sha256-j6kTzWlSPWwoyvOR6nJAx8VpikMsy/U3ZAJGI2PxPE0=",
|
||||
"aarch64-darwin": "sha256-H4XA72cYxaBBpT6wYAAAOhW4gHduUTxiqT/cNCjW5zc=",
|
||||
"x86_64-darwin": "sha256-4pljrneppSsxS5EYoXX0914L9z9jLHcKatm5T8L6/54="
|
||||
"x86_64-linux": "sha256-olTZ+tKugAY3LxizsJMlbK3TW78HZUoM03PigvQLP4A=",
|
||||
"aarch64-linux": "sha256-xdKDeqMEnYM2+vGySfb8pbcYyo/xMmgxG/ZhPCKaZEg=",
|
||||
"aarch64-darwin": "sha256-fihCTrHIiUG+py4vuqdr+YshqSKm2/B5onY50b97sPM=",
|
||||
"x86_64-darwin": "sha256-inlQQPNAOdkmKK6HQAMI2bG/ZFlfwmUQu9a6vm6Q0jQ="
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Popover as Kobalte } from "@kobalte/core/popover"
|
||||
import { Component, ComponentProps, createMemo, createSignal, JSX, Show, ValidComponent } from "solid-js"
|
||||
import { Component, ComponentProps, createEffect, createMemo, JSX, onCleanup, Show, ValidComponent } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useLocal } from "@/context/local"
|
||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { popularProviders } from "@/hooks/use-providers"
|
||||
|
|
@ -92,26 +93,118 @@ export function ModelSelectorPopover<T extends ValidComponent = "div">(props: {
|
|||
triggerAs?: T
|
||||
triggerProps?: ComponentProps<T>
|
||||
}) {
|
||||
const [open, setOpen] = createSignal(false)
|
||||
const [store, setStore] = createStore<{
|
||||
open: boolean
|
||||
dismiss: "escape" | "outside" | null
|
||||
trigger?: HTMLElement
|
||||
content?: HTMLElement
|
||||
}>({
|
||||
open: false,
|
||||
dismiss: null,
|
||||
trigger: undefined,
|
||||
content: undefined,
|
||||
})
|
||||
const dialog = useDialog()
|
||||
|
||||
const handleManage = () => {
|
||||
setOpen(false)
|
||||
setStore("open", false)
|
||||
dialog.show(() => <DialogManageModels />)
|
||||
}
|
||||
const language = useLanguage()
|
||||
|
||||
createEffect(() => {
|
||||
if (!store.open) return
|
||||
|
||||
const inside = (node: Node | null | undefined) => {
|
||||
if (!node) return false
|
||||
const el = store.content
|
||||
if (el && el.contains(node)) return true
|
||||
const anchor = store.trigger
|
||||
if (anchor && anchor.contains(node)) return true
|
||||
return false
|
||||
}
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key !== "Escape") return
|
||||
setStore("dismiss", "escape")
|
||||
setStore("open", false)
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
const onPointerDown = (event: PointerEvent) => {
|
||||
const target = event.target
|
||||
if (!(target instanceof Node)) return
|
||||
if (inside(target)) return
|
||||
setStore("dismiss", "outside")
|
||||
setStore("open", false)
|
||||
}
|
||||
|
||||
const onFocusIn = (event: FocusEvent) => {
|
||||
if (!store.content) return
|
||||
const target = event.target
|
||||
if (!(target instanceof Node)) return
|
||||
if (inside(target)) return
|
||||
setStore("dismiss", "outside")
|
||||
setStore("open", false)
|
||||
}
|
||||
|
||||
window.addEventListener("keydown", onKeyDown, true)
|
||||
window.addEventListener("pointerdown", onPointerDown, true)
|
||||
window.addEventListener("focusin", onFocusIn, true)
|
||||
|
||||
onCleanup(() => {
|
||||
window.removeEventListener("keydown", onKeyDown, true)
|
||||
window.removeEventListener("pointerdown", onPointerDown, true)
|
||||
window.removeEventListener("focusin", onFocusIn, true)
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<Kobalte open={open()} onOpenChange={setOpen} placement="top-start" gutter={8}>
|
||||
<Kobalte.Trigger as={props.triggerAs ?? "div"} {...(props.triggerProps as any)}>
|
||||
<Kobalte
|
||||
open={store.open}
|
||||
onOpenChange={(next) => {
|
||||
if (next) setStore("dismiss", null)
|
||||
setStore("open", next)
|
||||
}}
|
||||
modal={false}
|
||||
placement="top-start"
|
||||
gutter={8}
|
||||
>
|
||||
<Kobalte.Trigger
|
||||
ref={(el) => setStore("trigger", el)}
|
||||
as={props.triggerAs ?? "div"}
|
||||
{...(props.triggerProps as any)}
|
||||
>
|
||||
{props.children}
|
||||
</Kobalte.Trigger>
|
||||
<Kobalte.Portal>
|
||||
<Kobalte.Content class="w-72 h-80 flex flex-col rounded-md border border-border-base bg-surface-raised-stronger-non-alpha shadow-md z-50 outline-none overflow-hidden">
|
||||
<Kobalte.Content
|
||||
ref={(el) => setStore("content", el)}
|
||||
class="w-72 h-80 flex flex-col rounded-md border border-border-base bg-surface-raised-stronger-non-alpha shadow-md z-50 outline-none overflow-hidden"
|
||||
onEscapeKeyDown={(event) => {
|
||||
setStore("dismiss", "escape")
|
||||
setStore("open", false)
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}}
|
||||
onPointerDownOutside={() => {
|
||||
setStore("dismiss", "outside")
|
||||
setStore("open", false)
|
||||
}}
|
||||
onFocusOutside={() => {
|
||||
setStore("dismiss", "outside")
|
||||
setStore("open", false)
|
||||
}}
|
||||
onCloseAutoFocus={(event) => {
|
||||
if (store.dismiss === "outside") event.preventDefault()
|
||||
setStore("dismiss", null)
|
||||
}}
|
||||
>
|
||||
<Kobalte.Title class="sr-only">{language.t("dialog.model.select.title")}</Kobalte.Title>
|
||||
<ModelList
|
||||
provider={props.provider}
|
||||
onSelect={() => setOpen(false)}
|
||||
onSelect={() => setStore("open", false)}
|
||||
class="p-1"
|
||||
action={
|
||||
<IconButton
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ import { usePlatform } from "@/context/platform"
|
|||
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
|
||||
import { useNavigate } from "@solidjs/router"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { Popover } from "@opencode-ai/ui/popover"
|
||||
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||
import { useGlobalSDK } from "@/context/global-sdk"
|
||||
|
||||
type ServerStatus = { healthy: boolean; version?: string }
|
||||
|
|
@ -52,16 +53,27 @@ async function checkHealth(url: string, platform: ReturnType<typeof usePlatform>
|
|||
|
||||
function AddRow(props: AddRowProps) {
|
||||
return (
|
||||
<div class="flex items-center gap-3 px-4 min-w-0 flex-1">
|
||||
<div
|
||||
classList={{
|
||||
"size-1.5 rounded-full shrink-0": true,
|
||||
"bg-icon-success-base": props.status === true,
|
||||
"bg-icon-critical-base": props.status === false,
|
||||
"bg-border-weak-base": props.status === undefined,
|
||||
}}
|
||||
/>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center px-4 min-h-14 py-3 min-w-0 flex-1">
|
||||
<div class="flex-1 min-w-0 [&_[data-slot=input-wrapper]]:relative">
|
||||
<div
|
||||
classList={{
|
||||
"size-1.5 rounded-full absolute left-3 z-10 pointer-events-none": true,
|
||||
"bg-icon-success-base": props.status === true,
|
||||
"bg-icon-critical-base": props.status === false,
|
||||
"bg-border-weak-base": props.status === undefined,
|
||||
}}
|
||||
style={{ top: "50%", transform: "translateY(-50%)" }}
|
||||
ref={(el) => {
|
||||
// Position relative to input-wrapper
|
||||
requestAnimationFrame(() => {
|
||||
const wrapper = el.parentElement?.querySelector('[data-slot="input-wrapper"]')
|
||||
if (wrapper instanceof HTMLElement) {
|
||||
wrapper.style.position = "relative"
|
||||
wrapper.appendChild(el)
|
||||
}
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
type="text"
|
||||
hideLabel
|
||||
|
|
@ -74,6 +86,7 @@ function AddRow(props: AddRowProps) {
|
|||
onChange={props.onChange}
|
||||
onKeyDown={props.onKeyDown}
|
||||
onBlur={props.onBlur}
|
||||
class="pl-7"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -134,7 +147,14 @@ export function DialogSelectServer() {
|
|||
status: undefined as boolean | undefined,
|
||||
},
|
||||
})
|
||||
const [defaultUrl, defaultUrlActions] = createResource(() => platform.getDefaultServerUrl?.())
|
||||
const [defaultUrl, defaultUrlActions] = createResource(
|
||||
async () => {
|
||||
const url = await platform.getDefaultServerUrl?.()
|
||||
if (!url) return null
|
||||
return normalizeServerUrl(url) ?? null
|
||||
},
|
||||
{ initialValue: null },
|
||||
)
|
||||
const isDesktop = platform.platform === "desktop"
|
||||
|
||||
const looksComplete = (value: string) => {
|
||||
|
|
@ -344,17 +364,23 @@ export function DialogSelectServer() {
|
|||
|
||||
return (
|
||||
<Dialog title={language.t("dialog.server.title")}>
|
||||
<div class="flex flex-col gap-2 pb-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<List
|
||||
search={{ placeholder: language.t("dialog.server.search.placeholder"), autofocus: true }}
|
||||
search={{ placeholder: language.t("dialog.server.search.placeholder"), autofocus: false }}
|
||||
noInitialSelection
|
||||
emptyMessage={language.t("dialog.server.empty")}
|
||||
items={sortedItems}
|
||||
key={(x) => x}
|
||||
onSelect={(x) => {
|
||||
if (x) select(x)
|
||||
}}
|
||||
onFilter={(value) => {
|
||||
if (value && store.addServer.showForm && !store.addServer.adding) {
|
||||
resetAdd()
|
||||
}
|
||||
}}
|
||||
divider={true}
|
||||
class="[&_[data-slot=list-scroll]]:max-h-[300px] [&_[data-slot=list-scroll]]:overflow-y-auto [&_[data-slot=list-items]]:bg-surface-raised-base [&_[data-slot=list-items]]:rounded-md [&_[data-slot=list-item]]:py-3"
|
||||
class="px-5 [&_[data-slot=list-search-wrapper]]:w-full [&_[data-slot=list-scroll]]:max-h-[300px] [&_[data-slot=list-scroll]]:overflow-y-auto [&_[data-slot=list-items]]:bg-surface-raised-base [&_[data-slot=list-items]]:rounded-md [&_[data-slot=list-item]]:h-14 [&_[data-slot=list-item]]:p-3 [&_[data-slot=list-item]]:!bg-transparent [&_[data-slot=list-item-add]]:px-0"
|
||||
add={
|
||||
store.addServer.showForm
|
||||
? {
|
||||
|
|
@ -375,7 +401,35 @@ export function DialogSelectServer() {
|
|||
}
|
||||
>
|
||||
{(i) => {
|
||||
const [popoverOpen, setPopoverOpen] = createSignal(false)
|
||||
const [truncated, setTruncated] = createSignal(false)
|
||||
let nameRef: HTMLSpanElement | undefined
|
||||
let versionRef: HTMLSpanElement | undefined
|
||||
|
||||
const check = () => {
|
||||
const nameTruncated = nameRef ? nameRef.scrollWidth > nameRef.clientWidth : false
|
||||
const versionTruncated = versionRef ? versionRef.scrollWidth > versionRef.clientWidth : false
|
||||
setTruncated(nameTruncated || versionTruncated)
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
check()
|
||||
window.addEventListener("resize", check)
|
||||
onCleanup(() => window.removeEventListener("resize", check))
|
||||
})
|
||||
|
||||
const tooltipValue = () => {
|
||||
const name = serverDisplayName(i)
|
||||
const version = store.status[i]?.version
|
||||
return (
|
||||
<span class="flex items-center gap-2">
|
||||
<span>{name}</span>
|
||||
<Show when={version}>
|
||||
<span class="text-text-invert-base">{version}</span>
|
||||
</Show>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="flex items-center gap-3 min-w-0 flex-1 group/item">
|
||||
<Show
|
||||
|
|
@ -393,58 +447,54 @@ export function DialogSelectServer() {
|
|||
/>
|
||||
}
|
||||
>
|
||||
<div
|
||||
class="flex items-center gap-3 px-4 min-w-0 flex-1"
|
||||
classList={{ "opacity-50": store.status[i]?.healthy === false }}
|
||||
>
|
||||
<Tooltip value={tooltipValue()} placement="top" inactive={!truncated()}>
|
||||
<div
|
||||
classList={{
|
||||
"size-1.5 rounded-full shrink-0": true,
|
||||
"bg-icon-success-base": store.status[i]?.healthy === true,
|
||||
"bg-icon-critical-base": store.status[i]?.healthy === false,
|
||||
"bg-border-weak-base": store.status[i] === undefined,
|
||||
}}
|
||||
/>
|
||||
<span class="truncate">{serverDisplayName(i)}</span>
|
||||
<Show when={store.status[i]?.version}>
|
||||
<span class="text-text-weak text-14-regular">{store.status[i]?.version}</span>
|
||||
</Show>
|
||||
<Show when={defaultUrl() === i}>
|
||||
<span class="text-text-weak bg-surface-base text-14-regular px-1.5 rounded-xs">
|
||||
{language.t("dialog.server.status.default")}
|
||||
class="flex items-center gap-3 px-4 min-w-0 flex-1"
|
||||
classList={{ "opacity-50": store.status[i]?.healthy === false }}
|
||||
>
|
||||
<div
|
||||
classList={{
|
||||
"size-1.5 rounded-full shrink-0": true,
|
||||
"bg-icon-success-base": store.status[i]?.healthy === true,
|
||||
"bg-icon-critical-base": store.status[i]?.healthy === false,
|
||||
"bg-border-weak-base": store.status[i] === undefined,
|
||||
}}
|
||||
/>
|
||||
<span ref={nameRef} class="truncate">
|
||||
{serverDisplayName(i)}
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={store.status[i]?.version}>
|
||||
<span ref={versionRef} class="text-text-weak text-14-regular truncate">
|
||||
{store.status[i]?.version}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={defaultUrl() === i}>
|
||||
<span class="text-text-weak bg-surface-base text-14-regular px-1.5 rounded-xs">
|
||||
{language.t("dialog.server.status.default")}
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Show>
|
||||
<Show when={store.editServer.id !== i}>
|
||||
<div class="flex items-center justify-center gap-5 px-4">
|
||||
<div class="flex items-center justify-center gap-5 pl-4">
|
||||
<Show when={current() === i}>
|
||||
<p class="text-text-weak text-12-regular">{language.t("dialog.server.current")}</p>
|
||||
</Show>
|
||||
|
||||
<div onClick={(e) => e.stopPropagation()} onPointerDown={(e) => e.stopPropagation()}>
|
||||
<Popover
|
||||
open={popoverOpen()}
|
||||
onOpenChange={setPopoverOpen}
|
||||
placement="bottom-start"
|
||||
trigger={
|
||||
<IconButton
|
||||
icon="dot-grid"
|
||||
variant="ghost"
|
||||
class="bg-transparent transition-opacity shrink-0 hover:scale-110 size-8"
|
||||
onPointerDown={(event: PointerEvent) => event.stopPropagation()}
|
||||
/>
|
||||
}
|
||||
class="w-max !min-w-fit !max-w-none"
|
||||
>
|
||||
<div class="flex flex-col gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="normal"
|
||||
class="justify-start text-md"
|
||||
onClick={(e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
setPopoverOpen(false)
|
||||
<DropdownMenu>
|
||||
<DropdownMenu.Trigger
|
||||
as={IconButton}
|
||||
icon="dot-grid"
|
||||
variant="ghost"
|
||||
class="shrink-0 size-8 hover:bg-surface-base-hover data-[expanded]:bg-surface-base-active"
|
||||
onClick={(e: MouseEvent) => e.stopPropagation()}
|
||||
onPointerDown={(e: PointerEvent) => e.stopPropagation()}
|
||||
/>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content class="mt-1">
|
||||
<DropdownMenu.Item
|
||||
onSelect={() => {
|
||||
setStore("editServer", {
|
||||
id: i,
|
||||
value: i,
|
||||
|
|
@ -453,54 +503,42 @@ export function DialogSelectServer() {
|
|||
})
|
||||
}}
|
||||
>
|
||||
{language.t("dialog.server.menu.edit")}
|
||||
</Button>
|
||||
<DropdownMenu.ItemLabel>{language.t("dialog.server.menu.edit")}</DropdownMenu.ItemLabel>
|
||||
</DropdownMenu.Item>
|
||||
<Show when={isDesktop && defaultUrl() !== i}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="normal"
|
||||
class="justify-start text-md"
|
||||
onClick={async (e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
setPopoverOpen(false)
|
||||
<DropdownMenu.Item
|
||||
onSelect={async () => {
|
||||
await platform.setDefaultServerUrl?.(i)
|
||||
defaultUrlActions.refetch(i)
|
||||
defaultUrlActions.mutate(i)
|
||||
}}
|
||||
>
|
||||
{language.t("dialog.server.menu.default")}
|
||||
</Button>
|
||||
<DropdownMenu.ItemLabel>
|
||||
{language.t("dialog.server.menu.default")}
|
||||
</DropdownMenu.ItemLabel>
|
||||
</DropdownMenu.Item>
|
||||
</Show>
|
||||
<Show when={isDesktop && defaultUrl() === i}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="normal"
|
||||
class="justify-start text-md"
|
||||
onClick={async (e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
setPopoverOpen(false)
|
||||
<DropdownMenu.Item
|
||||
onSelect={async () => {
|
||||
await platform.setDefaultServerUrl?.(null)
|
||||
defaultUrlActions.refetch(null)
|
||||
defaultUrlActions.mutate(null)
|
||||
}}
|
||||
>
|
||||
{language.t("dialog.server.menu.defaultRemove")}
|
||||
</Button>
|
||||
<DropdownMenu.ItemLabel>
|
||||
{language.t("dialog.server.menu.defaultRemove")}
|
||||
</DropdownMenu.ItemLabel>
|
||||
</DropdownMenu.Item>
|
||||
</Show>
|
||||
<div class="h-px bg-border-weak-base my-1" />
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="normal"
|
||||
class="justify-start text-md text-text-on-critical-base hover:bg-surface-critical-weak"
|
||||
onClick={(e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
setPopoverOpen(false)
|
||||
handleRemove(i)
|
||||
}}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item
|
||||
onSelect={() => handleRemove(i)}
|
||||
class="text-text-on-critical-base hover:bg-surface-critical-weak"
|
||||
>
|
||||
{language.t("dialog.server.menu.delete")}
|
||||
</Button>
|
||||
</div>
|
||||
</Popover>
|
||||
</div>
|
||||
<DropdownMenu.ItemLabel>{language.t("dialog.server.menu.delete")}</DropdownMenu.ItemLabel>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
|
@ -508,7 +546,7 @@ export function DialogSelectServer() {
|
|||
}}
|
||||
</List>
|
||||
|
||||
<div class="px-6">
|
||||
<div class="px-5 pb-5">
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon="plus-small"
|
||||
|
|
@ -517,7 +555,7 @@ export function DialogSelectServer() {
|
|||
setStore("addServer", { showForm: true, url: "", error: "" })
|
||||
scrollListToBottom()
|
||||
}}
|
||||
class="px-3 py-4"
|
||||
class="py-1.5 pl-1.5 pr-3 flex items-center gap-1.5"
|
||||
>
|
||||
{store.addServer.adding ? language.t("dialog.server.add.checking") : language.t("dialog.server.add.button")}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -184,6 +184,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||
if (!item.commentID) return
|
||||
|
||||
comments.setFocus({ file: item.path, id: item.commentID })
|
||||
comments.setActive({ file: item.path, id: item.commentID })
|
||||
view().reviewPanel.open()
|
||||
|
||||
if (item.commentOrigin === "review") {
|
||||
|
|
@ -1711,15 +1712,19 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||
<div class="flex flex-nowrap items-start gap-2 p-2 overflow-x-auto no-scrollbar">
|
||||
<For each={prompt.context.items()}>
|
||||
{(item) => {
|
||||
const active = () => {
|
||||
const a = comments.active()
|
||||
return !!item.commentID && item.commentID === a?.id && item.path === a?.file
|
||||
}
|
||||
return (
|
||||
<Tooltip
|
||||
value={
|
||||
<span class="flex max-w-[300px]">
|
||||
<span
|
||||
class="text-text-invert-base truncate min-w-0"
|
||||
style={{ direction: "rtl", "text-align": "left" }}
|
||||
style={{ direction: "rtl", "text-align": "left", "unicode-bidi": "plaintext" }}
|
||||
>
|
||||
<bdi>{getDirectory(item.path)}</bdi>
|
||||
{getDirectory(item.path)}
|
||||
</span>
|
||||
<span class="shrink-0">{getFilename(item.path)}</span>
|
||||
</span>
|
||||
|
|
@ -1729,8 +1734,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||
>
|
||||
<div
|
||||
classList={{
|
||||
"group shrink-0 flex flex-col rounded-[6px] bg-background-stronger pl-2 pr-1 py-1 max-w-[200px] h-12 transition-all shadow-xs-border hover:shadow-xs-border-hover": true,
|
||||
"cursor-pointer hover:bg-surface-interactive-weak": !!item.commentID,
|
||||
"group shrink-0 flex flex-col rounded-[6px] pl-2 pr-1 py-1 max-w-[200px] h-12 transition-all transition-transform shadow-xs-border hover:shadow-xs-border-hover": true,
|
||||
"cursor-pointer hover:bg-surface-interactive-weak": !!item.commentID && !active(),
|
||||
"cursor-pointer bg-surface-interactive-hover hover:bg-surface-interactive-hover shadow-xs-border-hover":
|
||||
active(),
|
||||
"bg-background-stronger": !active(),
|
||||
}}
|
||||
onClick={() => {
|
||||
openComment(item)
|
||||
|
|
|
|||
|
|
@ -163,7 +163,10 @@ export function SessionHeader() {
|
|||
? language.t("session.share.popover.description.shared")
|
||||
: language.t("session.share.popover.description.unshared")
|
||||
}
|
||||
gutter={8}
|
||||
gutter={6}
|
||||
placement="bottom-end"
|
||||
shift={-64}
|
||||
class="rounded-xl [&_[data-slot=popover-close-button]]:hidden"
|
||||
triggerAs={Button}
|
||||
triggerProps={{
|
||||
variant: "secondary",
|
||||
|
|
@ -192,8 +195,8 @@ export function SessionHeader() {
|
|||
</div>
|
||||
}
|
||||
>
|
||||
<div class="flex flex-col gap-2 w-72">
|
||||
<TextField value={shareUrl() ?? ""} readOnly copyable class="w-full" />
|
||||
<div class="flex flex-col gap-2">
|
||||
<TextField value={shareUrl() ?? ""} readOnly copyable tabIndex={-1} class="w-full" />
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<Button
|
||||
size="large"
|
||||
|
|
@ -231,7 +234,7 @@ export function SessionHeader() {
|
|||
gutter={8}
|
||||
>
|
||||
<IconButton
|
||||
icon={state.copied ? "check" : "copy"}
|
||||
icon={state.copied ? "check" : "link"}
|
||||
variant="secondary"
|
||||
class="rounded-l-none"
|
||||
onClick={copyLink}
|
||||
|
|
@ -246,7 +249,7 @@ export function SessionHeader() {
|
|||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
<div class="hidden md:block shrink-0">
|
||||
<div class="hidden md:flex items-center gap-3 ml-2 shrink-0">
|
||||
<TooltipKeybind
|
||||
title={language.t("command.terminal.toggle")}
|
||||
keybind={command.keybind("terminal.toggle")}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { useSync } from "@/context/sync"
|
|||
import { useLanguage } from "@/context/language"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
||||
import { Select } from "@opencode-ai/ui/select"
|
||||
|
||||
const MAIN_WORKTREE = "main"
|
||||
const CREATE_WORKTREE = "create"
|
||||
|
|
@ -60,18 +59,7 @@ export function NewSessionView(props: NewSessionViewProps) {
|
|||
</div>
|
||||
<div class="flex justify-center items-center gap-1">
|
||||
<Icon name="branch" size="small" />
|
||||
<Select
|
||||
options={options()}
|
||||
current={current()}
|
||||
value={(x) => x}
|
||||
label={label}
|
||||
onSelect={(value) => {
|
||||
props.onWorktreeChange(value ?? MAIN_WORKTREE)
|
||||
}}
|
||||
size="normal"
|
||||
variant="ghost"
|
||||
class="text-12-medium"
|
||||
/>
|
||||
<div class="text-12-medium text-text-weak select-text ml-2">{label(current())}</div>
|
||||
</div>
|
||||
<Show when={sync.project}>
|
||||
{(project) => (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { createEffect, createMemo, createSignal, For, onCleanup, Show } from "solid-js"
|
||||
import { createEffect, createMemo, createSignal, For, onCleanup, onMount, Show } from "solid-js"
|
||||
import { createStore, reconcile } from "solid-js/store"
|
||||
import { useNavigate } from "@solidjs/router"
|
||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
|
|
@ -7,6 +7,7 @@ import { Tabs } from "@opencode-ai/ui/tabs"
|
|||
import { Button } from "@opencode-ai/ui/button"
|
||||
import { Switch } from "@opencode-ai/ui/switch"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||
import { useSync } from "@/context/sync"
|
||||
import { useSDK } from "@/context/sdk"
|
||||
import { normalizeServerUrl, serverDisplayName, useServer } from "@/context/server"
|
||||
|
|
@ -138,7 +139,8 @@ export function StatusPopover() {
|
|||
triggerAs={Button}
|
||||
triggerProps={{
|
||||
variant: "ghost",
|
||||
class: "rounded-sm w-[75px] h-[24px] py-1.5 pr-3 pl-2 gap-2 border-none shadow-none",
|
||||
class:
|
||||
"rounded-sm w-[75px] h-[24px] py-1.5 pr-3 pl-2 gap-2 border-none shadow-none data-[expanded]:bg-surface-raised-base-active",
|
||||
style: { scale: 1 },
|
||||
}}
|
||||
trigger={
|
||||
|
|
@ -154,10 +156,15 @@ export function StatusPopover() {
|
|||
<span class="text-12-regular text-text-strong">Status</span>
|
||||
</div>
|
||||
}
|
||||
class="[&_[data-slot=popover-body]]:p-0 w-[360px] max-w-[calc(100vw-40px)] mx-5 bg-transparent border-0 shadow-none rounded-xl"
|
||||
gutter={8}
|
||||
class="[&_[data-slot=popover-body]]:p-0 w-[360px] max-w-[calc(100vw-40px)] bg-transparent border-0 shadow-none rounded-xl"
|
||||
gutter={6}
|
||||
placement="bottom-end"
|
||||
shift={-136}
|
||||
>
|
||||
<div class="flex items-center gap-1 w-[360px] border border-border-weak-base rounded-xl">
|
||||
<div
|
||||
class="flex items-center gap-1 w-[360px] rounded-xl"
|
||||
style={{ "box-shadow": "var(--shadow-lg-border-base)" }}
|
||||
>
|
||||
<Tabs
|
||||
aria-label="Server Configurations"
|
||||
class="tabs"
|
||||
|
|
@ -197,58 +204,92 @@ export function StatusPopover() {
|
|||
|
||||
<Tabs.Content value="servers">
|
||||
<div class="flex flex-col px-2 pb-2">
|
||||
<div class="flex flex-col p-2 bg-background-base">
|
||||
<div class="flex flex-col p-3 bg-background-base rounded-sm min-h-14">
|
||||
<For each={sortedServers()}>
|
||||
{(url) => {
|
||||
const isActive = () => url === server.url
|
||||
const isDefault = () => url === defaultServerUrl()
|
||||
const status = () => store.status[url]
|
||||
const isBlocked = () => status()?.healthy === false
|
||||
const [truncated, setTruncated] = createSignal(false)
|
||||
let nameRef: HTMLSpanElement | undefined
|
||||
let versionRef: HTMLSpanElement | undefined
|
||||
|
||||
onMount(() => {
|
||||
const check = () => {
|
||||
const nameTruncated = nameRef ? nameRef.scrollWidth > nameRef.clientWidth : false
|
||||
const versionTruncated = versionRef ? versionRef.scrollWidth > versionRef.clientWidth : false
|
||||
setTruncated(nameTruncated || versionTruncated)
|
||||
}
|
||||
check()
|
||||
window.addEventListener("resize", check)
|
||||
onCleanup(() => window.removeEventListener("resize", check))
|
||||
})
|
||||
|
||||
const tooltipValue = () => {
|
||||
const name = serverDisplayName(url)
|
||||
const version = status()?.version
|
||||
return (
|
||||
<span class="flex items-center gap-2">
|
||||
<span>{name}</span>
|
||||
<Show when={version}>
|
||||
<span class="text-text-invert-base">{version}</span>
|
||||
</Show>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center gap-2 w-full px-2 py-1 rounded-md transition-colors text-left"
|
||||
classList={{
|
||||
"opacity-50": isBlocked(),
|
||||
"hover:bg-surface-raised-base-hover": !isBlocked(),
|
||||
"cursor-not-allowed": isBlocked(),
|
||||
}}
|
||||
aria-disabled={isBlocked()}
|
||||
onClick={() => {
|
||||
if (isBlocked()) return
|
||||
server.setActive(url)
|
||||
navigate("/")
|
||||
}}
|
||||
>
|
||||
<div
|
||||
<Tooltip value={tooltipValue()} placement="top" inactive={!truncated()}>
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center gap-2 w-full h-8 pl-3 pr-1.5 py-1.5 rounded-md transition-colors text-left"
|
||||
classList={{
|
||||
"size-1.5 rounded-full shrink-0": true,
|
||||
"bg-icon-success-base": status()?.healthy === true,
|
||||
"bg-icon-critical-base": status()?.healthy === false,
|
||||
"bg-border-weak-base": status() === undefined,
|
||||
"opacity-50": isBlocked(),
|
||||
"hover:bg-surface-raised-base-hover": !isBlocked(),
|
||||
"cursor-not-allowed": isBlocked(),
|
||||
}}
|
||||
/>
|
||||
<span class="text-14-regular text-text-base truncate">{serverDisplayName(url)}</span>
|
||||
<Show when={status()?.version}>
|
||||
<span class="text-12-regular text-text-weak">{status()?.version}</span>
|
||||
</Show>
|
||||
<Show when={isDefault()}>
|
||||
<span class="text-11-regular text-text-base bg-surface-base px-1.5 py-0.5 rounded-md">
|
||||
Default
|
||||
aria-disabled={isBlocked()}
|
||||
onClick={() => {
|
||||
if (isBlocked()) return
|
||||
server.setActive(url)
|
||||
navigate("/")
|
||||
}}
|
||||
>
|
||||
<div
|
||||
classList={{
|
||||
"size-1.5 rounded-full shrink-0": true,
|
||||
"bg-icon-success-base": status()?.healthy === true,
|
||||
"bg-icon-critical-base": status()?.healthy === false,
|
||||
"bg-border-weak-base": status() === undefined,
|
||||
}}
|
||||
/>
|
||||
<span ref={nameRef} class="text-14-regular text-text-base truncate">
|
||||
{serverDisplayName(url)}
|
||||
</span>
|
||||
</Show>
|
||||
<div class="flex-1" />
|
||||
<Show when={isActive()}>
|
||||
<Icon name="check" size="small" class="text-icon-weak shrink-0" />
|
||||
</Show>
|
||||
</button>
|
||||
<Show when={status()?.version}>
|
||||
<span ref={versionRef} class="text-12-regular text-text-weak truncate">
|
||||
{status()?.version}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={isDefault()}>
|
||||
<span class="text-11-regular text-text-base bg-surface-base px-1.5 py-0.5 rounded-md">
|
||||
Default
|
||||
</span>
|
||||
</Show>
|
||||
<div class="flex-1" />
|
||||
<Show when={isActive()}>
|
||||
<Icon name="check" size="small" class="text-icon-weak shrink-0" />
|
||||
</Show>
|
||||
</button>
|
||||
</Tooltip>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
|
||||
<Button
|
||||
variant="secondary"
|
||||
class="mt-2 self-start"
|
||||
class="mt-3 self-start h-8 px-3 py-1.5"
|
||||
onClick={() => dialog.show(() => <DialogSelectServer />)}
|
||||
>
|
||||
Manage servers
|
||||
|
|
@ -259,11 +300,11 @@ export function StatusPopover() {
|
|||
|
||||
<Tabs.Content value="mcp">
|
||||
<div class="flex flex-col px-2 pb-2">
|
||||
<div class="flex flex-col p-2 bg-background-base">
|
||||
<div class="flex flex-col p-3 bg-background-base rounded-sm min-h-14">
|
||||
<Show
|
||||
when={mcpItems().length > 0}
|
||||
fallback={
|
||||
<div class="text-14-regular text-text-weak text-center py-4">No MCP servers configured</div>
|
||||
<div class="text-14-regular text-text-base text-center my-auto">No MCP servers configured</div>
|
||||
}
|
||||
>
|
||||
<For each={mcpItems()}>
|
||||
|
|
@ -272,7 +313,7 @@ export function StatusPopover() {
|
|||
return (
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center gap-2 w-full px-2 py-1 rounded-md hover:bg-surface-raised-base-hover transition-colors text-left"
|
||||
class="flex items-center gap-2 w-full h-8 pl-3 pr-2 py-1 rounded-md hover:bg-surface-raised-base-hover transition-colors text-left"
|
||||
onClick={() => toggleMcp(item.name)}
|
||||
disabled={loading() === item.name}
|
||||
>
|
||||
|
|
@ -305,11 +346,11 @@ export function StatusPopover() {
|
|||
|
||||
<Tabs.Content value="lsp">
|
||||
<div class="flex flex-col px-2 pb-2">
|
||||
<div class="flex flex-col p-2 bg-background-base">
|
||||
<div class="flex flex-col p-3 bg-background-base rounded-sm min-h-14">
|
||||
<Show
|
||||
when={lspItems().length > 0}
|
||||
fallback={
|
||||
<div class="text-14-regular text-text-weak text-center py-4">
|
||||
<div class="text-14-regular text-text-base text-center my-auto">
|
||||
LSPs auto-detected from file types
|
||||
</div>
|
||||
}
|
||||
|
|
@ -335,13 +376,13 @@ export function StatusPopover() {
|
|||
|
||||
<Tabs.Content value="plugins">
|
||||
<div class="flex flex-col px-2 pb-2">
|
||||
<div class="flex flex-col p-2 bg-background-base">
|
||||
<div class="flex flex-col p-3 bg-background-base rounded-sm min-h-14">
|
||||
<Show
|
||||
when={plugins().length > 0}
|
||||
fallback={
|
||||
<div class="text-14-regular text-text-weak text-center py-4">
|
||||
<div class="text-14-regular text-text-base text-center my-auto">
|
||||
Plugins configured in{" "}
|
||||
<code class="bg-surface-raised-base px-1.5 py-0.5 rounded-sm">opencode.json</code>
|
||||
<code class="bg-surface-raised-base px-1.5 py-0.5 rounded-sm text-text-base">opencode.json</code>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -111,6 +111,8 @@ export const Terminal = (props: TerminalProps) => {
|
|||
const mod = await import("ghostty-web")
|
||||
ghostty = await mod.Ghostty.load()
|
||||
|
||||
const once = { value: false }
|
||||
|
||||
const url = new URL(sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`)
|
||||
if (window.__OPENCODE__?.serverPassword) {
|
||||
url.username = "opencode"
|
||||
|
|
@ -258,6 +260,8 @@ export const Terminal = (props: TerminalProps) => {
|
|||
})
|
||||
socket.addEventListener("error", (error) => {
|
||||
if (disposed) return
|
||||
if (once.value) return
|
||||
once.value = true
|
||||
console.error("WebSocket error:", error)
|
||||
local.onConnectError?.(error)
|
||||
})
|
||||
|
|
@ -266,6 +270,8 @@ export const Terminal = (props: TerminalProps) => {
|
|||
// Normal closure (code 1000) means PTY process exited - server event handles cleanup
|
||||
// For other codes (network issues, server restart), trigger error handler
|
||||
if (event.code !== 1000) {
|
||||
if (once.value) return
|
||||
once.value = true
|
||||
local.onConnectError?.(new Error(`WebSocket closed abnormally: ${event.code}`))
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ export function Titlebar() {
|
|||
classList={{
|
||||
"flex items-center w-full min-w-0": true,
|
||||
"pl-2": !mac(),
|
||||
"pr-2": !windows(),
|
||||
"pr-6": !windows(),
|
||||
}}
|
||||
onMouseDown={drag}
|
||||
data-tauri-drag-region
|
||||
|
|
@ -145,6 +145,7 @@ export function Titlebar() {
|
|||
data-tauri-drag-region
|
||||
/>
|
||||
<Show when={windows()}>
|
||||
<div class="w-6 shrink-0" />
|
||||
<div data-tauri-decorum-tb class="flex flex-row" />
|
||||
</Show>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ function createCommentSession(dir: string, id: string | undefined) {
|
|||
)
|
||||
|
||||
const [focus, setFocus] = createSignal<CommentFocus | null>(null)
|
||||
const [active, setActive] = createSignal<CommentFocus | null>(null)
|
||||
|
||||
const list = (file: string) => store.comments[file] ?? []
|
||||
|
||||
|
|
@ -76,6 +77,9 @@ function createCommentSession(dir: string, id: string | undefined) {
|
|||
focus: createMemo(() => focus()),
|
||||
setFocus,
|
||||
clearFocus: () => setFocus(null),
|
||||
active: createMemo(() => active()),
|
||||
setActive,
|
||||
clearActive: () => setActive(null),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -135,6 +139,9 @@ export const { use: useComments, provider: CommentsProvider } = createSimpleCont
|
|||
focus: () => session().focus(),
|
||||
setFocus: (focus: CommentFocus | null) => session().setFocus(focus),
|
||||
clearFocus: () => session().clearFocus(),
|
||||
active: () => session().active(),
|
||||
setActive: (active: CommentFocus | null) => session().setActive(active),
|
||||
clearActive: () => session().clearActive(),
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ export type LocalPTY = {
|
|||
cols?: number
|
||||
buffer?: string
|
||||
scrollY?: number
|
||||
error?: boolean
|
||||
}
|
||||
|
||||
const WORKSPACE_KEY = "__workspace__"
|
||||
|
|
@ -151,13 +150,18 @@ function createTerminalSession(sdk: ReturnType<typeof useSDK>, dir: string, sess
|
|||
return undefined
|
||||
})
|
||||
if (!clone?.data) return
|
||||
setStore("all", index, {
|
||||
...pty,
|
||||
...clone.data,
|
||||
|
||||
const active = store.active === pty.id
|
||||
|
||||
batch(() => {
|
||||
setStore("all", index, {
|
||||
...pty,
|
||||
...clone.data,
|
||||
})
|
||||
if (active) {
|
||||
setStore("active", clone.data.id)
|
||||
}
|
||||
})
|
||||
if (store.active === pty.id) {
|
||||
setStore("active", clone.data.id)
|
||||
}
|
||||
},
|
||||
open(id: string) {
|
||||
setStore("active", id)
|
||||
|
|
|
|||
|
|
@ -1,16 +1,4 @@
|
|||
import {
|
||||
For,
|
||||
Index,
|
||||
onCleanup,
|
||||
onMount,
|
||||
Show,
|
||||
Match,
|
||||
Switch,
|
||||
createMemo,
|
||||
createEffect,
|
||||
on,
|
||||
createSignal,
|
||||
} from "solid-js"
|
||||
import { For, onCleanup, onMount, Show, Match, Switch, createMemo, createEffect, on, createSignal } from "solid-js"
|
||||
import { createMediaQuery } from "@solid-primitives/media"
|
||||
import { createResizeObserver } from "@solid-primitives/resize-observer"
|
||||
import { Dynamic } from "solid-js/web"
|
||||
|
|
@ -41,7 +29,6 @@ import { useLayout } from "@/context/layout"
|
|||
import { Terminal } from "@/components/terminal"
|
||||
import { checksum, base64Encode, base64Decode } from "@opencode-ai/util/encode"
|
||||
import { findLast } from "@opencode-ai/util/array"
|
||||
import { getFilename } from "@opencode-ai/util/path"
|
||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { DialogSelectFile } from "@/components/dialog-select-file"
|
||||
import { DialogSelectModel } from "@/components/dialog-select-model"
|
||||
|
|
@ -67,7 +54,6 @@ import {
|
|||
SortableTerminalTab,
|
||||
NewSessionView,
|
||||
} from "@/components/session"
|
||||
import { usePlatform } from "@/context/platform"
|
||||
import { navMark, navParams } from "@/utils/perf"
|
||||
import { same } from "@/utils/same"
|
||||
|
||||
|
|
@ -103,7 +89,7 @@ function SessionReviewTab(props: SessionReviewTabProps) {
|
|||
|
||||
const sdk = useSDK()
|
||||
|
||||
const readFile = (path: string) => {
|
||||
const readFile = async (path: string) => {
|
||||
return sdk.client.file
|
||||
.read({ path })
|
||||
.then((x) => x.data)
|
||||
|
|
@ -192,7 +178,6 @@ export default function Page() {
|
|||
const codeComponent = useCodeComponent()
|
||||
const command = useCommand()
|
||||
const language = useLanguage()
|
||||
const platform = usePlatform()
|
||||
const params = useParams()
|
||||
const navigate = useNavigate()
|
||||
const sdk = useSDK()
|
||||
|
|
@ -1537,9 +1522,9 @@ export default function Page() {
|
|||
}}
|
||||
onClick={autoScroll.handleInteraction}
|
||||
class="relative min-w-0 w-full h-full overflow-y-auto session-scroller"
|
||||
style={{ "--session-title-height": info()?.title ? "40px" : "0px" }}
|
||||
style={{ "--session-title-height": info()?.title || info()?.parentID ? "40px" : "0px" }}
|
||||
>
|
||||
<Show when={info()?.title}>
|
||||
<Show when={info()?.title || info()?.parentID}>
|
||||
<div
|
||||
classList={{
|
||||
"sticky top-0 z-30 bg-background-stronger": true,
|
||||
|
|
@ -1548,8 +1533,21 @@ export default function Page() {
|
|||
"md:max-w-200 md:mx-auto": !showTabs(),
|
||||
}}
|
||||
>
|
||||
<div class="h-10 flex items-center">
|
||||
<h1 class="text-16-medium text-text-strong truncate">{info()?.title}</h1>
|
||||
<div class="h-10 flex items-center gap-1">
|
||||
<Show when={info()?.parentID}>
|
||||
<IconButton
|
||||
tabIndex={-1}
|
||||
icon="arrow-left"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
navigate(`/${params.dir}/session/${info()?.parentID}`)
|
||||
}}
|
||||
aria-label={language.t("common.goBack")}
|
||||
/>
|
||||
</Show>
|
||||
<Show when={info()?.title}>
|
||||
<h1 class="text-16-medium text-text-strong truncate">{info()?.title}</h1>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
|
@ -2453,66 +2451,23 @@ export default function Page() {
|
|||
</Tabs>
|
||||
<div class="flex-1 min-h-0 relative">
|
||||
<For each={terminal.all()}>
|
||||
{(pty) => {
|
||||
const [dismissed, setDismissed] = createSignal(false)
|
||||
return (
|
||||
<div
|
||||
id={`terminal-wrapper-${pty.id}`}
|
||||
class="absolute inset-0"
|
||||
style={{
|
||||
display: terminal.active() === pty.id ? "block" : "none",
|
||||
}}
|
||||
>
|
||||
{(pty) => (
|
||||
<div
|
||||
id={`terminal-wrapper-${pty.id}`}
|
||||
class="absolute inset-0"
|
||||
style={{
|
||||
display: terminal.active() === pty.id ? "block" : "none",
|
||||
}}
|
||||
>
|
||||
<Show when={pty.id} keyed>
|
||||
<Terminal
|
||||
pty={pty}
|
||||
onCleanup={(data) => terminal.update({ ...data, id: pty.id })}
|
||||
onConnect={() => {
|
||||
terminal.update({ id: pty.id, error: false })
|
||||
setDismissed(false)
|
||||
}}
|
||||
onConnectError={() => {
|
||||
setDismissed(false)
|
||||
terminal.update({ id: pty.id, error: true })
|
||||
}}
|
||||
onCleanup={terminal.update}
|
||||
onConnectError={() => terminal.clone(pty.id)}
|
||||
/>
|
||||
<Show when={pty.error && !dismissed()}>
|
||||
<div
|
||||
class="absolute inset-0 flex flex-col items-center justify-center gap-3"
|
||||
style={{ "background-color": "rgba(0, 0, 0, 0.6)" }}
|
||||
>
|
||||
<Icon
|
||||
name="circle-ban-sign"
|
||||
class="w-8 h-8"
|
||||
style={{ color: "rgba(239, 68, 68, 0.8)" }}
|
||||
/>
|
||||
<div class="text-center" style={{ color: "rgba(255, 255, 255, 0.7)" }}>
|
||||
<div class="text-14-semibold mb-1">{language.t("terminal.connectionLost.title")}</div>
|
||||
<div class="text-12-regular" style={{ color: "rgba(255, 255, 255, 0.5)" }}>
|
||||
{language.t("terminal.connectionLost.description")}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="mt-2 px-3 py-1.5 text-12-medium rounded-lg transition-colors"
|
||||
style={{
|
||||
"background-color": "rgba(255, 255, 255, 0.1)",
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
border: "1px solid rgba(255, 255, 255, 0.2)",
|
||||
}}
|
||||
onMouseEnter={(e) =>
|
||||
(e.currentTarget.style.backgroundColor = "rgba(255, 255, 255, 0.15)")
|
||||
}
|
||||
onMouseLeave={(e) =>
|
||||
(e.currentTarget.style.backgroundColor = "rgba(255, 255, 255, 0.1)")
|
||||
}
|
||||
onClick={() => setDismissed(true)}
|
||||
>
|
||||
{language.t("common.dismiss")}
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</Show>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"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.1.34",
|
||||
"version": "1.1.35",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
id = "opencode"
|
||||
name = "OpenCode"
|
||||
description = "The open source coding agent."
|
||||
version = "1.1.34"
|
||||
version = "1.1.35"
|
||||
schema_version = 1
|
||||
authors = ["Anomaly"]
|
||||
repository = "https://github.com/anomalyco/opencode"
|
||||
|
|
@ -11,26 +11,26 @@ name = "OpenCode"
|
|||
icon = "./icons/opencode.svg"
|
||||
|
||||
[agent_servers.opencode.targets.darwin-aarch64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.34/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.35/opencode-darwin-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.darwin-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.34/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.35/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.34/opencode-linux-arm64.tar.gz"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.35/opencode-linux-arm64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.34/opencode-linux-x64.tar.gz"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.35/opencode-linux-x64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.windows-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.34/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.35/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export namespace Question {
|
|||
|
||||
export const Option = z
|
||||
.object({
|
||||
label: z.string().max(30).describe("Display text (1-5 words, concise)"),
|
||||
label: z.string().describe("Display text (1-5 words, concise)"),
|
||||
description: z.string().describe("Explanation of choice"),
|
||||
})
|
||||
.meta({
|
||||
|
|
@ -21,7 +21,7 @@ export namespace Question {
|
|||
export const Info = z
|
||||
.object({
|
||||
question: z.string().describe("Complete question"),
|
||||
header: z.string().max(30).describe("Very short label (max 30 chars)"),
|
||||
header: z.string().describe("Very short label (max 30 chars)"),
|
||||
options: z.array(Option).describe("Available choices"),
|
||||
multiple: z.boolean().optional().describe("Allow selecting multiple choices"),
|
||||
custom: z.boolean().optional().describe("Allow typing a custom answer (default: true)"),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
"dist"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "0.90.4",
|
||||
"@hey-api/openapi-ts": "0.90.10",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
|
|
|
|||
|
|
@ -2518,7 +2518,17 @@ export class Control extends HeyApiClient {
|
|||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }, { in: "body" }] }])
|
||||
const params = buildClientParams(
|
||||
[parameters],
|
||||
[
|
||||
{
|
||||
args: [
|
||||
{ in: "query", key: "directory" },
|
||||
{ key: "body", map: "body" },
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
return (options?.client ?? this.client).post<TuiControlResponseResponses, unknown, ThrowOnError>({
|
||||
url: "/tui/control/response",
|
||||
...options,
|
||||
|
|
@ -2770,7 +2780,17 @@ export class Tui extends HeyApiClient {
|
|||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }, { in: "body" }] }])
|
||||
const params = buildClientParams(
|
||||
[parameters],
|
||||
[
|
||||
{
|
||||
args: [
|
||||
{ in: "query", key: "directory" },
|
||||
{ key: "body", map: "body" },
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
return (options?.client ?? this.client).post<TuiPublishResponses, TuiPublishErrors, ThrowOnError>({
|
||||
url: "/tui/publish",
|
||||
...options,
|
||||
|
|
|
|||
|
|
@ -7319,8 +7319,7 @@
|
|||
"properties": {
|
||||
"label": {
|
||||
"description": "Display text (1-5 words, concise)",
|
||||
"type": "string",
|
||||
"maxLength": 30
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "Explanation of choice",
|
||||
|
|
@ -7338,8 +7337,7 @@
|
|||
},
|
||||
"header": {
|
||||
"description": "Very short label (max 30 chars)",
|
||||
"type": "string",
|
||||
"maxLength": 30
|
||||
"type": "string"
|
||||
},
|
||||
"options": {
|
||||
"description": "Available choices",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
|
|
@ -56,6 +56,7 @@
|
|||
"marked": "catalog:",
|
||||
"marked-katex-extension": "5.1.6",
|
||||
"marked-shiki": "catalog:",
|
||||
"morphdom": "2.7.8",
|
||||
"remeda": "catalog:",
|
||||
"shiki": "catalog:",
|
||||
"solid-js": "catalog:",
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@
|
|||
|
||||
[data-slot="dialog-header"] {
|
||||
display: flex;
|
||||
padding: 16px 16px 16px 24px;
|
||||
padding: 20px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ const icons = {
|
|||
keyboard: `<path d="M5.125 7.375V4.375H14.875V2.875M8.3125 13.9375H11.6875M8.125 13.9375H11.875M2.125 7.375H17.875V17.125H2.125V7.375ZM5.5 10.375H5.125V10.75H5.5V10.375ZM8.5 10.375H8.125V10.75H8.5V10.375ZM11.875 10.375H11.5V10.75H11.875V10.375ZM14.875 10.375H14.5V10.75H14.875V10.375ZM14.875 13.75H14.5V14.125H14.875V13.75ZM5.5 13.75H5.125V14.125H5.5V13.75Z" stroke="currentColor" stroke-linecap="square"/>`,
|
||||
selector: `<path d="M6.66626 12.5033L9.99959 15.8366L13.3329 12.5033M6.66626 7.50326L9.99959 4.16992L13.3329 7.50326" stroke="currentColor" stroke-linecap="square"/>`,
|
||||
"arrow-down-to-line": `<path d="M15.2083 11.6667L10 16.875L4.79167 11.6667M10 16.25V3.125" stroke="currentColor" stroke-width="1.25" stroke-linecap="square"/>`,
|
||||
link: `<path d="M2.08334 12.0833L1.72979 11.7298L1.37624 12.0833L1.72979 12.4369L2.08334 12.0833ZM7.91668 17.9167L7.56312 18.2702L7.91668 18.6238L8.27023 18.2702L7.91668 17.9167ZM17.9167 7.91666L18.2702 8.27022L18.6238 7.91666L18.2702 7.56311L17.9167 7.91666ZM12.0833 2.08333L12.4369 1.72977L12.0833 1.37622L11.7298 1.72977L12.0833 2.08333ZM8.39646 5.06311L8.0429 5.41666L8.75001 6.12377L9.10356 5.77021L8.75001 5.41666L8.39646 5.06311ZM5.77023 9.10355L6.12378 8.74999L5.41668 8.04289L5.06312 8.39644L5.41668 8.74999L5.77023 9.10355ZM14.2298 10.8964L13.8762 11.25L14.5833 11.9571L14.9369 11.6035L14.5833 11.25L14.2298 10.8964ZM11.6036 14.9369L11.9571 14.5833L11.25 13.8762L10.8965 14.2298L11.25 14.5833L11.6036 14.9369ZM7.14646 12.1464L6.7929 12.5L7.50001 13.2071L7.85356 12.8535L7.50001 12.5L7.14646 12.1464ZM12.8536 7.85355L13.2071 7.49999L12.5 6.79289L12.1465 7.14644L12.5 7.49999L12.8536 7.85355ZM2.08334 12.0833L1.72979 12.4369L7.56312 18.2702L7.91668 17.9167L8.27023 17.5631L2.4369 11.7298L2.08334 12.0833ZM17.9167 7.91666L18.2702 7.56311L12.4369 1.72977L12.0833 2.08333L11.7298 2.43688L17.5631 8.27022L17.9167 7.91666ZM12.0833 2.08333L11.7298 1.72977L8.39646 5.06311L8.75001 5.41666L9.10356 5.77021L12.4369 2.43688L12.0833 2.08333ZM5.41668 8.74999L5.06312 8.39644L1.72979 11.7298L2.08334 12.0833L2.4369 12.4369L5.77023 9.10355L5.41668 8.74999ZM14.5833 11.25L14.9369 11.6035L18.2702 8.27022L17.9167 7.91666L17.5631 7.56311L14.2298 10.8964L14.5833 11.25ZM7.91668 17.9167L8.27023 18.2702L11.6036 14.9369L11.25 14.5833L10.8965 14.2298L7.56312 17.5631L7.91668 17.9167ZM7.50001 12.5L7.85356 12.8535L12.8536 7.85355L12.5 7.49999L12.1465 7.14644L7.14646 12.1464L7.50001 12.5Z" fill="currentColor"/>`,
|
||||
}
|
||||
|
||||
export interface IconProps extends ComponentProps<"svg"> {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@
|
|||
z-index: var(--line-comment-z, 30);
|
||||
}
|
||||
|
||||
[data-component="line-comment"][data-open] {
|
||||
z-index: var(--line-comment-open-z, 100);
|
||||
}
|
||||
|
||||
[data-component="line-comment"] [data-slot="line-comment-button"] {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ export const LineCommentAnchor = (props: LineCommentAnchorProps) => {
|
|||
data-component="line-comment"
|
||||
data-variant={variant()}
|
||||
data-comment-id={props.id}
|
||||
data-open={props.open ? "" : undefined}
|
||||
classList={{
|
||||
[props.class ?? ""]: !!props.class,
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@
|
|||
gap: 8px;
|
||||
align-self: stretch;
|
||||
margin-bottom: 4px;
|
||||
padding-right: 4px;
|
||||
|
||||
> [data-component="icon-button"] {
|
||||
width: 24px;
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ export interface ListProps<T> extends FilteredListProps<T> {
|
|||
loadingMessage?: string
|
||||
onKeyEvent?: (event: KeyboardEvent, item: T | undefined) => void
|
||||
onMove?: (item: T | undefined) => void
|
||||
onFilter?: (value: string) => void
|
||||
activeIcon?: IconProps["name"]
|
||||
filter?: string
|
||||
search?: ListSearchProps | boolean
|
||||
|
|
@ -98,6 +99,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
|
|||
const current = internalFilter()
|
||||
if (prev !== current) {
|
||||
onInput(current)
|
||||
props.onFilter?.(current)
|
||||
}
|
||||
return current
|
||||
}, "")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useMarked } from "../context/marked"
|
||||
import { useI18n } from "../context/i18n"
|
||||
import DOMPurify from "dompurify"
|
||||
import morphdom from "morphdom"
|
||||
import { checksum } from "@opencode-ai/util/encode"
|
||||
import { ComponentProps, createEffect, createResource, createSignal, onCleanup, splitProps } from "solid-js"
|
||||
import { isServer } from "solid-js/web"
|
||||
|
|
@ -194,18 +195,61 @@ export function Markdown(
|
|||
{ initialValue: "" },
|
||||
)
|
||||
|
||||
let copySetupTimer: ReturnType<typeof setTimeout> | undefined
|
||||
let copyCleanup: (() => void) | undefined
|
||||
|
||||
createEffect(() => {
|
||||
const container = root()
|
||||
const content = html()
|
||||
if (!container) return
|
||||
if (!content) return
|
||||
if (isServer) return
|
||||
const cleanup = setupCodeCopy(container, {
|
||||
copy: i18n.t("ui.message.copy"),
|
||||
copied: i18n.t("ui.message.copied"),
|
||||
|
||||
if (!content) {
|
||||
container.innerHTML = ""
|
||||
return
|
||||
}
|
||||
|
||||
const temp = document.createElement("div")
|
||||
temp.innerHTML = content
|
||||
|
||||
morphdom(container, temp, {
|
||||
childrenOnly: true,
|
||||
onBeforeElUpdated: (fromEl, toEl) => {
|
||||
if (fromEl.isEqualNode(toEl)) return false
|
||||
if (fromEl.getAttribute("data-component") === "markdown-code") {
|
||||
const fromPre = fromEl.querySelector("pre")
|
||||
const toPre = toEl.querySelector("pre")
|
||||
if (fromPre && toPre && !fromPre.isEqualNode(toPre)) {
|
||||
morphdom(fromPre, toPre)
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
onBeforeNodeDiscarded: (node) => {
|
||||
if (node instanceof Element) {
|
||||
if (node.getAttribute("data-slot") === "markdown-copy-button") return false
|
||||
if (node.getAttribute("data-component") === "markdown-code") return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
})
|
||||
onCleanup(cleanup)
|
||||
|
||||
if (copySetupTimer) clearTimeout(copySetupTimer)
|
||||
copySetupTimer = setTimeout(() => {
|
||||
if (copyCleanup) copyCleanup()
|
||||
copyCleanup = setupCodeCopy(container, {
|
||||
copy: i18n.t("ui.message.copy"),
|
||||
copied: i18n.t("ui.message.copied"),
|
||||
})
|
||||
}, 150)
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
if (copySetupTimer) clearTimeout(copySetupTimer)
|
||||
if (copyCleanup) copyCleanup()
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
data-component="markdown"
|
||||
|
|
@ -213,7 +257,6 @@ export function Markdown(
|
|||
...(local.classList ?? {}),
|
||||
[local.class ?? ""]: !!local.class,
|
||||
}}
|
||||
innerHTML={html.latest}
|
||||
ref={setRoot}
|
||||
{...others}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,15 @@
|
|||
import { Popover as Kobalte } from "@kobalte/core/popover"
|
||||
import { ComponentProps, JSXElement, ParentProps, Show, splitProps, ValidComponent } from "solid-js"
|
||||
import {
|
||||
ComponentProps,
|
||||
JSXElement,
|
||||
ParentProps,
|
||||
Show,
|
||||
createEffect,
|
||||
createSignal,
|
||||
onCleanup,
|
||||
splitProps,
|
||||
ValidComponent,
|
||||
} from "solid-js"
|
||||
import { useI18n } from "../context/i18n"
|
||||
import { IconButton } from "./icon-button"
|
||||
|
||||
|
|
@ -13,6 +23,7 @@ export interface PopoverProps<T extends ValidComponent = "div">
|
|||
description?: JSXElement
|
||||
class?: ComponentProps<"div">["class"]
|
||||
classList?: ComponentProps<"div">["classList"]
|
||||
style?: ComponentProps<"div">["style"]
|
||||
portal?: boolean
|
||||
}
|
||||
|
||||
|
|
@ -26,17 +37,96 @@ export function Popover<T extends ValidComponent = "div">(props: PopoverProps<T>
|
|||
"description",
|
||||
"class",
|
||||
"classList",
|
||||
"style",
|
||||
"children",
|
||||
"portal",
|
||||
"open",
|
||||
"defaultOpen",
|
||||
"onOpenChange",
|
||||
"modal",
|
||||
])
|
||||
|
||||
const [contentRef, setContentRef] = createSignal<HTMLElement | undefined>(undefined)
|
||||
const [triggerRef, setTriggerRef] = createSignal<HTMLElement | undefined>(undefined)
|
||||
const [dismiss, setDismiss] = createSignal<"escape" | "outside" | null>(null)
|
||||
|
||||
const [uncontrolledOpen, setUncontrolledOpen] = createSignal<boolean>(local.defaultOpen ?? false)
|
||||
|
||||
const controlled = () => local.open !== undefined
|
||||
const opened = () => {
|
||||
if (controlled()) return local.open ?? false
|
||||
return uncontrolledOpen()
|
||||
}
|
||||
|
||||
const onOpenChange = (next: boolean) => {
|
||||
if (next) setDismiss(null)
|
||||
if (local.onOpenChange) local.onOpenChange(next)
|
||||
if (controlled()) return
|
||||
setUncontrolledOpen(next)
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
if (!opened()) return
|
||||
|
||||
const inside = (node: Node | null | undefined) => {
|
||||
if (!node) return false
|
||||
const content = contentRef()
|
||||
if (content && content.contains(node)) return true
|
||||
const trigger = triggerRef()
|
||||
if (trigger && trigger.contains(node)) return true
|
||||
return false
|
||||
}
|
||||
|
||||
const close = (reason: "escape" | "outside") => {
|
||||
setDismiss(reason)
|
||||
onOpenChange(false)
|
||||
}
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key !== "Escape") return
|
||||
close("escape")
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
const onPointerDown = (event: PointerEvent) => {
|
||||
const target = event.target
|
||||
if (!(target instanceof Node)) return
|
||||
if (inside(target)) return
|
||||
close("outside")
|
||||
}
|
||||
|
||||
const onFocusIn = (event: FocusEvent) => {
|
||||
const target = event.target
|
||||
if (!(target instanceof Node)) return
|
||||
if (inside(target)) return
|
||||
close("outside")
|
||||
}
|
||||
|
||||
window.addEventListener("keydown", onKeyDown, true)
|
||||
window.addEventListener("pointerdown", onPointerDown, true)
|
||||
window.addEventListener("focusin", onFocusIn, true)
|
||||
|
||||
onCleanup(() => {
|
||||
window.removeEventListener("keydown", onKeyDown, true)
|
||||
window.removeEventListener("pointerdown", onPointerDown, true)
|
||||
window.removeEventListener("focusin", onFocusIn, true)
|
||||
})
|
||||
})
|
||||
|
||||
const content = () => (
|
||||
<Kobalte.Content
|
||||
ref={(el: HTMLElement | undefined) => setContentRef(el)}
|
||||
data-component="popover-content"
|
||||
classList={{
|
||||
...(local.classList ?? {}),
|
||||
[local.class ?? ""]: !!local.class,
|
||||
}}
|
||||
style={local.style}
|
||||
onCloseAutoFocus={(event: Event) => {
|
||||
if (dismiss() === "outside") event.preventDefault()
|
||||
setDismiss(null)
|
||||
}}
|
||||
>
|
||||
{/* <Kobalte.Arrow data-slot="popover-arrow" /> */}
|
||||
<Show when={local.title}>
|
||||
|
|
@ -59,8 +149,13 @@ export function Popover<T extends ValidComponent = "div">(props: PopoverProps<T>
|
|||
)
|
||||
|
||||
return (
|
||||
<Kobalte gutter={4} {...rest}>
|
||||
<Kobalte.Trigger as={local.triggerAs ?? "div"} data-slot="popover-trigger" {...(local.triggerProps as any)}>
|
||||
<Kobalte gutter={4} {...rest} open={opened()} onOpenChange={onOpenChange} modal={local.modal ?? false}>
|
||||
<Kobalte.Trigger
|
||||
ref={(el: HTMLElement) => setTriggerRef(el)}
|
||||
as={local.triggerAs ?? "div"}
|
||||
data-slot="popover-trigger"
|
||||
{...(local.triggerProps as any)}
|
||||
>
|
||||
{local.trigger}
|
||||
</Kobalte.Trigger>
|
||||
<Show when={local.portal ?? true} fallback={content()}>
|
||||
|
|
|
|||
|
|
@ -267,6 +267,6 @@
|
|||
position: relative;
|
||||
overflow: hidden;
|
||||
--line-comment-z: 5;
|
||||
--line-comment-popover-z: 6;
|
||||
--line-comment-popover-z: 30;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,6 +63,12 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 14px 24px;
|
||||
outline: none;
|
||||
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="tabs-trigger-close-button"] {
|
||||
|
|
@ -81,7 +87,7 @@
|
|||
}
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px var(--border-focus);
|
||||
box-shadow: none;
|
||||
}
|
||||
&:has([data-hidden]) {
|
||||
[data-slot="tabs-trigger-close-button"] {
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@
|
|||
border: 1px solid var(--border-weak-base);
|
||||
background: var(--input-base);
|
||||
|
||||
&:focus-within {
|
||||
&:focus-within:not(:has([data-readonly])) {
|
||||
border-color: transparent;
|
||||
/* border/shadow-xs/select */
|
||||
box-shadow:
|
||||
|
|
|
|||
|
|
@ -93,17 +93,20 @@ export function TextField(props: TextFieldProps) {
|
|||
</Show>
|
||||
<Show when={local.copyable}>
|
||||
<Tooltip
|
||||
value={copied() ? i18n.t("ui.textField.copied") : i18n.t("ui.textField.copyToClipboard")}
|
||||
value={copied() ? i18n.t("ui.textField.copied") : i18n.t("ui.textField.copyLink")}
|
||||
placement="top"
|
||||
gutter={8}
|
||||
gutter={4}
|
||||
forceOpen={copied()}
|
||||
skipDelayDuration={0}
|
||||
>
|
||||
<IconButton
|
||||
type="button"
|
||||
icon={copied() ? "check" : "copy"}
|
||||
icon={copied() ? "check" : "link"}
|
||||
variant="ghost"
|
||||
onClick={handleCopy}
|
||||
tabIndex={-1}
|
||||
data-slot="input-copy-button"
|
||||
aria-label={copied() ? i18n.t("ui.textField.copied") : i18n.t("ui.textField.copyToClipboard")}
|
||||
aria-label={copied() ? i18n.t("ui.textField.copied") : i18n.t("ui.textField.copyLink")}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Show>
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
/* transform: translate3d(0, 0, 0); */
|
||||
}
|
||||
|
||||
&[data-closed] {
|
||||
&[data-closed]:not([data-force-open="true"]) {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export interface TooltipProps extends ComponentProps<typeof KobalteTooltip> {
|
|||
contentClass?: string
|
||||
contentStyle?: JSX.CSSProperties
|
||||
inactive?: boolean
|
||||
forceOpen?: boolean
|
||||
}
|
||||
|
||||
export interface TooltipKeybindProps extends Omit<TooltipProps, "value"> {
|
||||
|
|
@ -32,7 +33,14 @@ export function TooltipKeybind(props: TooltipKeybindProps) {
|
|||
|
||||
export function Tooltip(props: TooltipProps) {
|
||||
const [open, setOpen] = createSignal(false)
|
||||
const [local, others] = splitProps(props, ["children", "class", "contentClass", "contentStyle", "inactive"])
|
||||
const [local, others] = splitProps(props, [
|
||||
"children",
|
||||
"class",
|
||||
"contentClass",
|
||||
"contentStyle",
|
||||
"inactive",
|
||||
"forceOpen",
|
||||
])
|
||||
|
||||
const c = children(() => local.children)
|
||||
|
||||
|
|
@ -55,7 +63,7 @@ export function Tooltip(props: TooltipProps) {
|
|||
<Switch>
|
||||
<Match when={local.inactive}>{local.children}</Match>
|
||||
<Match when={true}>
|
||||
<KobalteTooltip forceMount gutter={4} {...others} open={open()} onOpenChange={setOpen}>
|
||||
<KobalteTooltip forceMount gutter={4} {...others} open={local.forceOpen || open()} onOpenChange={setOpen}>
|
||||
<KobalteTooltip.Trigger as={"div"} data-component="tooltip-trigger" class={local.class}>
|
||||
{c()}
|
||||
</KobalteTooltip.Trigger>
|
||||
|
|
@ -63,6 +71,7 @@ export function Tooltip(props: TooltipProps) {
|
|||
<KobalteTooltip.Content
|
||||
data-component="tooltip"
|
||||
data-placement={props.placement}
|
||||
data-force-open={local.forceOpen}
|
||||
class={local.contentClass}
|
||||
style={local.contentStyle}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|||
return el.scrollHeight - el.clientHeight - el.scrollTop
|
||||
}
|
||||
|
||||
const canScroll = (el: HTMLElement) => {
|
||||
return el.scrollHeight - el.clientHeight > 1
|
||||
}
|
||||
|
||||
// Browsers can dispatch scroll events asynchronously. If new content arrives
|
||||
// between us calling `scrollTo()` and the subsequent `scroll` event firing,
|
||||
// the handler can see a non-zero `distanceFromBottom` and incorrectly assume
|
||||
|
|
@ -89,6 +93,12 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|||
}
|
||||
|
||||
const stop = () => {
|
||||
const el = scroll
|
||||
if (!el) return
|
||||
if (!canScroll(el)) {
|
||||
if (store.userScrolled) setStore("userScrolled", false)
|
||||
return
|
||||
}
|
||||
if (store.userScrolled) return
|
||||
|
||||
setStore("userScrolled", true)
|
||||
|
|
@ -111,6 +121,11 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|||
const el = scroll
|
||||
if (!el) return
|
||||
|
||||
if (!canScroll(el)) {
|
||||
if (store.userScrolled) setStore("userScrolled", false)
|
||||
return
|
||||
}
|
||||
|
||||
if (distanceFromBottom(el) < threshold()) {
|
||||
if (store.userScrolled) setStore("userScrolled", false)
|
||||
return
|
||||
|
|
@ -149,6 +164,11 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|||
createResizeObserver(
|
||||
() => store.contentRef,
|
||||
() => {
|
||||
const el = scroll
|
||||
if (el && !canScroll(el)) {
|
||||
if (store.userScrolled) setStore("userScrolled", false)
|
||||
return
|
||||
}
|
||||
if (!active()) return
|
||||
if (store.userScrolled) return
|
||||
// ResizeObserver fires after layout, before paint.
|
||||
|
|
@ -159,7 +179,7 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|||
)
|
||||
|
||||
createEffect(
|
||||
on(options.working, (working) => {
|
||||
on(options.working, (working: boolean) => {
|
||||
settling = false
|
||||
if (settleTimer) clearTimeout(settleTimer)
|
||||
settleTimer = undefined
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export interface FilteredListProps<T> {
|
|||
sortBy?: (a: T, b: T) => number
|
||||
sortGroupsBy?: (a: { category: string; items: T[] }, b: { category: string; items: T[] }) => number
|
||||
onSelect?: (value: T | undefined, index: number) => void
|
||||
noInitialSelection?: boolean
|
||||
}
|
||||
|
||||
export function useFilteredList<T>(props: FilteredListProps<T>) {
|
||||
|
|
@ -57,6 +58,7 @@ export function useFilteredList<T>(props: FilteredListProps<T>) {
|
|||
})
|
||||
|
||||
function initialActive() {
|
||||
if (props.noInitialSelection) return ""
|
||||
if (props.current) return props.key(props.current)
|
||||
|
||||
const items = flat()
|
||||
|
|
@ -71,6 +73,10 @@ export function useFilteredList<T>(props: FilteredListProps<T>) {
|
|||
})
|
||||
|
||||
const reset = () => {
|
||||
if (props.noInitialSelection) {
|
||||
list.setActive("")
|
||||
return
|
||||
}
|
||||
const all = flat()
|
||||
if (all.length === 0) return
|
||||
list.setActive(props.key(all[0]))
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ export const dict = {
|
|||
"ui.messageNav.newMessage": "رسالة جديدة",
|
||||
|
||||
"ui.textField.copyToClipboard": "نسخ إلى الحافظة",
|
||||
"ui.textField.copyLink": "نسخ الرابط",
|
||||
"ui.textField.copied": "تم النسخ",
|
||||
|
||||
"ui.imagePreview.alt": "معاينة الصورة",
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ export const dict = {
|
|||
"ui.messageNav.newMessage": "Nova mensagem",
|
||||
|
||||
"ui.textField.copyToClipboard": "Copiar para área de transferência",
|
||||
"ui.textField.copyLink": "Copiar link",
|
||||
"ui.textField.copied": "Copiado",
|
||||
|
||||
"ui.imagePreview.alt": "Visualização de imagem",
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ export const dict = {
|
|||
"ui.messageNav.newMessage": "Ny besked",
|
||||
|
||||
"ui.textField.copyToClipboard": "Kopier til udklipsholder",
|
||||
"ui.textField.copyLink": "Kopier link",
|
||||
"ui.textField.copied": "Kopieret",
|
||||
|
||||
"ui.imagePreview.alt": "Billedforhåndsvisning",
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ export const dict = {
|
|||
"ui.messageNav.newMessage": "Neue Nachricht",
|
||||
|
||||
"ui.textField.copyToClipboard": "In die Zwischenablage kopieren",
|
||||
"ui.textField.copyLink": "Link kopieren",
|
||||
"ui.textField.copied": "Kopiert",
|
||||
|
||||
"ui.imagePreview.alt": "Bildvorschau",
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ export const dict = {
|
|||
"ui.messageNav.newMessage": "New message",
|
||||
|
||||
"ui.textField.copyToClipboard": "Copy to clipboard",
|
||||
"ui.textField.copyLink": "Copy link",
|
||||
"ui.textField.copied": "Copied",
|
||||
|
||||
"ui.imagePreview.alt": "Image preview",
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ export const dict = {
|
|||
"ui.messageNav.newMessage": "Nuevo mensaje",
|
||||
|
||||
"ui.textField.copyToClipboard": "Copiar al portapapeles",
|
||||
"ui.textField.copyLink": "Copiar enlace",
|
||||
"ui.textField.copied": "Copiado",
|
||||
|
||||
"ui.imagePreview.alt": "Vista previa de imagen",
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ export const dict = {
|
|||
"ui.messageNav.newMessage": "Nouveau message",
|
||||
|
||||
"ui.textField.copyToClipboard": "Copier dans le presse-papiers",
|
||||
"ui.textField.copyLink": "Copier le lien",
|
||||
"ui.textField.copied": "Copié",
|
||||
|
||||
"ui.imagePreview.alt": "Aperçu de l'image",
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ export const dict = {
|
|||
"ui.messageNav.newMessage": "新しいメッセージ",
|
||||
|
||||
"ui.textField.copyToClipboard": "クリップボードにコピー",
|
||||
"ui.textField.copyLink": "リンクをコピー",
|
||||
"ui.textField.copied": "コピーしました",
|
||||
|
||||
"ui.imagePreview.alt": "画像プレビュー",
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ export const dict = {
|
|||
"ui.messageNav.newMessage": "새 메시지",
|
||||
|
||||
"ui.textField.copyToClipboard": "클립보드에 복사",
|
||||
"ui.textField.copyLink": "링크 복사",
|
||||
"ui.textField.copied": "복사됨",
|
||||
|
||||
"ui.imagePreview.alt": "이미지 미리보기",
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ export const dict: Record<Keys, string> = {
|
|||
"ui.messageNav.newMessage": "Ny melding",
|
||||
|
||||
"ui.textField.copyToClipboard": "Kopier til utklippstavlen",
|
||||
"ui.textField.copyLink": "Kopier lenke",
|
||||
"ui.textField.copied": "Kopiert",
|
||||
|
||||
"ui.imagePreview.alt": "Bildeforhåndsvisning",
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ export const dict = {
|
|||
"ui.messageNav.newMessage": "Nowa wiadomość",
|
||||
|
||||
"ui.textField.copyToClipboard": "Skopiuj do schowka",
|
||||
"ui.textField.copyLink": "Skopiuj link",
|
||||
"ui.textField.copied": "Skopiowano",
|
||||
|
||||
"ui.imagePreview.alt": "Podgląd obrazu",
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ export const dict = {
|
|||
"ui.messageNav.newMessage": "Новое сообщение",
|
||||
|
||||
"ui.textField.copyToClipboard": "Копировать в буфер обмена",
|
||||
"ui.textField.copyLink": "Копировать ссылку",
|
||||
"ui.textField.copied": "Скопировано",
|
||||
|
||||
"ui.imagePreview.alt": "Предпросмотр изображения",
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ export const dict = {
|
|||
"ui.messageNav.newMessage": "新消息",
|
||||
|
||||
"ui.textField.copyToClipboard": "复制到剪贴板",
|
||||
"ui.textField.copyLink": "复制链接",
|
||||
"ui.textField.copied": "已复制",
|
||||
|
||||
"ui.imagePreview.alt": "图片预览",
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ export const dict = {
|
|||
"ui.messageNav.newMessage": "新訊息",
|
||||
|
||||
"ui.textField.copyToClipboard": "複製到剪貼簿",
|
||||
"ui.textField.copyLink": "複製連結",
|
||||
"ui.textField.copied": "已複製",
|
||||
|
||||
"ui.imagePreview.alt": "圖片預覽",
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@
|
|||
--surface-brand-base: var(--yuzu-light-9);
|
||||
--surface-brand-hover: var(--yuzu-light-10);
|
||||
--surface-interactive-base: var(--cobalt-light-3);
|
||||
--surface-interactive-hover: var(--cobalt-light-4);
|
||||
--surface-interactive-hover: #e5f0ff;
|
||||
--surface-interactive-weak: var(--cobalt-light-2);
|
||||
--surface-interactive-weak-hover: var(--cobalt-light-3);
|
||||
--surface-success-base: var(--apple-light-3);
|
||||
|
|
@ -376,9 +376,9 @@
|
|||
--surface-raised-stronger-non-alpha: var(--smoke-dark-3);
|
||||
--surface-brand-base: var(--yuzu-light-9);
|
||||
--surface-brand-hover: var(--yuzu-light-10);
|
||||
--surface-interactive-base: var(--cobalt-light-3);
|
||||
--surface-interactive-hover: var(--cobalt-light-4);
|
||||
--surface-interactive-weak: var(--cobalt-light-2);
|
||||
--surface-interactive-base: var(--cobalt-dark-3);
|
||||
--surface-interactive-hover: #0a1d4d;
|
||||
--surface-interactive-weak: var(--cobalt-dark-2);
|
||||
--surface-interactive-weak-hover: var(--cobalt-light-3);
|
||||
--surface-success-base: var(--apple-light-3);
|
||||
--surface-success-weak: var(--apple-light-2);
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@
|
|||
"surface-brand-base": "var(--yuzu-light-9)",
|
||||
"surface-brand-hover": "var(--yuzu-light-10)",
|
||||
"surface-interactive-base": "var(--cobalt-light-3)",
|
||||
"surface-interactive-hover": "var(--cobalt-light-4)",
|
||||
"surface-interactive-hover": "#E5F0FF",
|
||||
"surface-interactive-weak": "var(--cobalt-light-2)",
|
||||
"surface-interactive-weak-hover": "var(--cobalt-light-3)",
|
||||
"surface-success-base": "var(--apple-light-3)",
|
||||
|
|
@ -311,9 +311,9 @@
|
|||
"surface-raised-stronger-non-alpha": "var(--smoke-dark-3)",
|
||||
"surface-brand-base": "var(--yuzu-light-9)",
|
||||
"surface-brand-hover": "var(--yuzu-light-10)",
|
||||
"surface-interactive-base": "var(--cobalt-light-3)",
|
||||
"surface-interactive-hover": "var(--cobalt-light-4)",
|
||||
"surface-interactive-weak": "var(--cobalt-light-2)",
|
||||
"surface-interactive-base": "var(--cobalt-dark-3)",
|
||||
"surface-interactive-hover": "#0A1D4D",
|
||||
"surface-interactive-weak": "var(--cobalt-dark-2)",
|
||||
"surface-interactive-weak-hover": "var(--cobalt-light-3)",
|
||||
"surface-success-base": "var(--apple-dark-3)",
|
||||
"surface-success-weak": "var(--apple-dark-2)",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"name": "@opencode-ai/web",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "1.1.34",
|
||||
"version": "1.1.35",
|
||||
"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.1.34",
|
||||
"version": "1.1.35",
|
||||
"publisher": "sst-dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
|||
Loading…
Reference in New Issue