mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-24 05:35:15 +00:00
Fix UX issues
This commit is contained in:
parent
d8d7b432ae
commit
b7b57a0172
7 changed files with 180 additions and 167 deletions
|
|
@ -2,9 +2,7 @@
|
|||
"version": "7",
|
||||
"dialect": "sqlite",
|
||||
"id": "27114226-085b-421a-9a40-29b88747e29a",
|
||||
"prevIds": [
|
||||
"aaa2ebeb-caa4-478d-8365-4fc595d16856"
|
||||
],
|
||||
"prevIds": ["aaa2ebeb-caa4-478d-8365-4fc595d16856"],
|
||||
"ddl": [
|
||||
{
|
||||
"name": "account_state",
|
||||
|
|
@ -1063,13 +1061,9 @@
|
|||
"table": "event"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"active_account_id"
|
||||
],
|
||||
"columns": ["active_account_id"],
|
||||
"tableTo": "account",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "SET NULL",
|
||||
"nameExplicit": false,
|
||||
|
|
@ -1078,13 +1072,9 @@
|
|||
"table": "account_state"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"project_id"
|
||||
],
|
||||
"columns": ["project_id"],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
|
|
@ -1093,13 +1083,9 @@
|
|||
"table": "workspace"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"session_id"
|
||||
],
|
||||
"columns": ["session_id"],
|
||||
"tableTo": "session",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
|
|
@ -1108,13 +1094,9 @@
|
|||
"table": "message"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"message_id"
|
||||
],
|
||||
"columns": ["message_id"],
|
||||
"tableTo": "message",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
|
|
@ -1123,13 +1105,9 @@
|
|||
"table": "part"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"project_id"
|
||||
],
|
||||
"columns": ["project_id"],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
|
|
@ -1138,13 +1116,9 @@
|
|||
"table": "permission"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"session_id"
|
||||
],
|
||||
"columns": ["session_id"],
|
||||
"tableTo": "session",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
|
|
@ -1153,13 +1127,9 @@
|
|||
"table": "session_entry"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"project_id"
|
||||
],
|
||||
"columns": ["project_id"],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
|
|
@ -1168,13 +1138,9 @@
|
|||
"table": "session"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"session_id"
|
||||
],
|
||||
"columns": ["session_id"],
|
||||
"tableTo": "session",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
|
|
@ -1183,13 +1149,9 @@
|
|||
"table": "todo"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"session_id"
|
||||
],
|
||||
"columns": ["session_id"],
|
||||
"tableTo": "session",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
|
|
@ -1198,13 +1160,9 @@
|
|||
"table": "session_share"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"aggregate_id"
|
||||
],
|
||||
"columns": ["aggregate_id"],
|
||||
"tableTo": "event_sequence",
|
||||
"columnsTo": [
|
||||
"aggregate_id"
|
||||
],
|
||||
"columnsTo": ["aggregate_id"],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
|
|
@ -1213,128 +1171,98 @@
|
|||
"table": "event"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"email",
|
||||
"url"
|
||||
],
|
||||
"columns": ["email", "url"],
|
||||
"nameExplicit": false,
|
||||
"name": "control_account_pk",
|
||||
"entityType": "pks",
|
||||
"table": "control_account"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"session_id",
|
||||
"position"
|
||||
],
|
||||
"columns": ["session_id", "position"],
|
||||
"nameExplicit": false,
|
||||
"name": "todo_pk",
|
||||
"entityType": "pks",
|
||||
"table": "todo"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"columns": ["id"],
|
||||
"nameExplicit": false,
|
||||
"name": "account_state_pk",
|
||||
"table": "account_state",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"columns": ["id"],
|
||||
"nameExplicit": false,
|
||||
"name": "account_pk",
|
||||
"table": "account",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"columns": ["id"],
|
||||
"nameExplicit": false,
|
||||
"name": "workspace_pk",
|
||||
"table": "workspace",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"columns": ["id"],
|
||||
"nameExplicit": false,
|
||||
"name": "project_pk",
|
||||
"table": "project",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"columns": ["id"],
|
||||
"nameExplicit": false,
|
||||
"name": "message_pk",
|
||||
"table": "message",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"columns": ["id"],
|
||||
"nameExplicit": false,
|
||||
"name": "part_pk",
|
||||
"table": "part",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"project_id"
|
||||
],
|
||||
"columns": ["project_id"],
|
||||
"nameExplicit": false,
|
||||
"name": "permission_pk",
|
||||
"table": "permission",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"columns": ["id"],
|
||||
"nameExplicit": false,
|
||||
"name": "session_entry_pk",
|
||||
"table": "session_entry",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"columns": ["id"],
|
||||
"nameExplicit": false,
|
||||
"name": "session_pk",
|
||||
"table": "session",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"session_id"
|
||||
],
|
||||
"columns": ["session_id"],
|
||||
"nameExplicit": false,
|
||||
"name": "session_share_pk",
|
||||
"table": "session_share",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"aggregate_id"
|
||||
],
|
||||
"columns": ["aggregate_id"],
|
||||
"nameExplicit": false,
|
||||
"name": "event_sequence_pk",
|
||||
"table": "event_sequence",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"columns": ["id"],
|
||||
"nameExplicit": false,
|
||||
"name": "event_pk",
|
||||
"table": "event",
|
||||
|
|
@ -1498,4 +1426,4 @@
|
|||
}
|
||||
],
|
||||
"renames": []
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { Flag } from "@opencode-ai/core/flag/flag"
|
|||
import { DialogSessionRename } from "./dialog-session-rename"
|
||||
import { createDebouncedSignal } from "../util/signal"
|
||||
import { useToast } from "../ui/toast"
|
||||
import { DialogWorkspaceSelect, type WorkspaceSelection, warpWorkspaceSession } from "./dialog-workspace-create"
|
||||
import { openWorkspaceSelect, type WorkspaceSelection, warpWorkspaceSession } from "./dialog-workspace-create"
|
||||
import { Spinner } from "./spinner"
|
||||
import { errorMessage } from "@/util/error"
|
||||
import { DialogSessionDeleteFailed } from "./dialog-session-delete-failed"
|
||||
|
|
@ -101,13 +101,15 @@ export function DialogSessionList() {
|
|||
return true
|
||||
}}
|
||||
onRestore={() => {
|
||||
dialog.replace(() => (
|
||||
<DialogWorkspaceSelect
|
||||
onSelect={(selection) => {
|
||||
void warp(selection)
|
||||
}}
|
||||
/>
|
||||
))
|
||||
void openWorkspaceSelect({
|
||||
dialog,
|
||||
sdk,
|
||||
sync,
|
||||
toast,
|
||||
onSelect: (selection) => {
|
||||
void warp(selection)
|
||||
},
|
||||
})
|
||||
return false
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -30,9 +30,41 @@ export type WorkspaceSelection =
|
|||
workspaceName: string
|
||||
}
|
||||
|
||||
type WorkspaceSelectValue = WorkspaceSelection | { type: "existing-list" } | { type: "loading" }
|
||||
type WorkspaceSelectValue = WorkspaceSelection | { type: "existing-list" }
|
||||
type ExistingWorkspaceSelectValue = { workspace: Workspace }
|
||||
|
||||
async function loadWorkspaceAdaptors(input: {
|
||||
sdk: ReturnType<typeof useSDK>
|
||||
sync: ReturnType<typeof useSync>
|
||||
toast: ReturnType<typeof useToast>
|
||||
}) {
|
||||
const dir = input.sync.path.directory || input.sdk.directory
|
||||
const url = new URL("/experimental/workspace/adaptor", input.sdk.url)
|
||||
if (dir) url.searchParams.set("directory", dir)
|
||||
const res = await input.sdk
|
||||
.fetch(url)
|
||||
.then((x) => x.json() as Promise<Adaptor[]>)
|
||||
.catch(() => undefined)
|
||||
if (res) return res
|
||||
input.toast.show({
|
||||
message: "Failed to load workspace adaptors",
|
||||
variant: "error",
|
||||
})
|
||||
}
|
||||
|
||||
export async function openWorkspaceSelect(input: {
|
||||
dialog: ReturnType<typeof useDialog>
|
||||
sdk: ReturnType<typeof useSDK>
|
||||
sync: ReturnType<typeof useSync>
|
||||
toast: ReturnType<typeof useToast>
|
||||
onSelect: (selection: WorkspaceSelection) => Promise<void> | void
|
||||
}) {
|
||||
input.dialog.clear()
|
||||
const adaptors = await loadWorkspaceAdaptors(input)
|
||||
if (!adaptors) return
|
||||
input.dialog.replace(() => <DialogWorkspaceSelect adaptors={adaptors} onSelect={input.onSelect} />)
|
||||
}
|
||||
|
||||
export async function warpWorkspaceSession(input: {
|
||||
dialog: ReturnType<typeof useDialog>
|
||||
sdk: ReturnType<typeof useSDK>
|
||||
|
|
@ -77,7 +109,7 @@ export async function warpWorkspaceSession(input: {
|
|||
}
|
||||
|
||||
export function DialogWorkspaceSelect(props: {
|
||||
current?: WorkspaceSelection
|
||||
adaptors?: Adaptor[]
|
||||
onSelect: (selection: WorkspaceSelection) => Promise<void> | void
|
||||
}) {
|
||||
const dialog = useDialog()
|
||||
|
|
@ -85,41 +117,21 @@ export function DialogWorkspaceSelect(props: {
|
|||
const sync = useSync()
|
||||
const sdk = useSDK()
|
||||
const toast = useToast()
|
||||
const [adaptors, setAdaptors] = createSignal<Adaptor[]>()
|
||||
const [adaptors, setAdaptors] = createSignal<Adaptor[] | undefined>(props.adaptors)
|
||||
|
||||
onMount(() => {
|
||||
dialog.setSize("medium")
|
||||
void (async () => {
|
||||
const dir = sync.path.directory || sdk.directory
|
||||
const url = new URL("/experimental/workspace/adaptor", sdk.url)
|
||||
if (dir) url.searchParams.set("directory", dir)
|
||||
const res = await sdk
|
||||
.fetch(url)
|
||||
.then((x) => x.json() as Promise<Adaptor[]>)
|
||||
.catch(() => undefined)
|
||||
if (!res) {
|
||||
toast.show({
|
||||
message: "Failed to load workspace adaptors",
|
||||
variant: "error",
|
||||
})
|
||||
return
|
||||
}
|
||||
if (adaptors()) return
|
||||
const res = await loadWorkspaceAdaptors({ sdk, sync, toast })
|
||||
if (!res) return
|
||||
setAdaptors(res)
|
||||
})()
|
||||
})
|
||||
|
||||
const options = createMemo<DialogSelectOption<WorkspaceSelectValue>[]>(() => {
|
||||
const list = adaptors()
|
||||
if (!list) {
|
||||
return [
|
||||
{
|
||||
title: "Loading workspaces...",
|
||||
value: { type: "loading" as const },
|
||||
description: "Fetching available workspace adaptors",
|
||||
category: "New workspace",
|
||||
},
|
||||
]
|
||||
}
|
||||
if (!list) return []
|
||||
const recent = sync.data.session
|
||||
.toSorted((a, b) => b.time.updated - a.time.updated)
|
||||
.flatMap((session) => (session.workspaceID ? [session.workspaceID] : []))
|
||||
|
|
@ -162,13 +174,13 @@ export function DialogWorkspaceSelect(props: {
|
|||
]
|
||||
})
|
||||
|
||||
if (!adaptors()) return null
|
||||
return (
|
||||
<DialogSelect<WorkspaceSelectValue>
|
||||
title="Warp"
|
||||
skipFilter={true}
|
||||
renderFilter={false}
|
||||
options={options()}
|
||||
current={props.current}
|
||||
onSelect={(option) => {
|
||||
if (!option.value) return
|
||||
if (option.value.type === "none") {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ import { useKV } from "../../context/kv"
|
|||
import { createFadeIn } from "../../util/signal"
|
||||
import { useTextareaKeybindings } from "../textarea-keybindings"
|
||||
import { DialogSkill } from "../dialog-skill"
|
||||
import { DialogWorkspaceSelect, warpWorkspaceSession, type WorkspaceSelection } from "../dialog-workspace-create"
|
||||
import { openWorkspaceSelect, warpWorkspaceSession, type WorkspaceSelection } from "../dialog-workspace-create"
|
||||
import { DialogWorkspaceUnavailable } from "../dialog-workspace-unavailable"
|
||||
import { useArgs } from "@tui/context/args"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
|
|
@ -557,14 +557,15 @@ export function Prompt(props: PromptProps) {
|
|||
name: "warp",
|
||||
},
|
||||
onSelect: (dialog) => {
|
||||
dialog.replace(() => (
|
||||
<DialogWorkspaceSelect
|
||||
current={selectedWorkspace()}
|
||||
onSelect={(selection) => {
|
||||
void warpSession(selection)
|
||||
}}
|
||||
/>
|
||||
))
|
||||
void openWorkspaceSelect({
|
||||
dialog,
|
||||
sdk,
|
||||
sync,
|
||||
toast,
|
||||
onSelect: (selection) => {
|
||||
void warpSession(selection)
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
]
|
||||
|
|
@ -815,14 +816,15 @@ export function Prompt(props: PromptProps) {
|
|||
dialog.replace(() => (
|
||||
<DialogWorkspaceUnavailable
|
||||
onRestore={() => {
|
||||
dialog.replace(() => (
|
||||
<DialogWorkspaceSelect
|
||||
current={selectedWorkspace()}
|
||||
onSelect={(selection) => {
|
||||
void warpSession(selection)
|
||||
}}
|
||||
/>
|
||||
))
|
||||
void openWorkspaceSelect({
|
||||
dialog,
|
||||
sdk,
|
||||
sync,
|
||||
toast,
|
||||
onSelect: (selection) => {
|
||||
void warpSession(selection)
|
||||
},
|
||||
})
|
||||
return false
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -75,10 +75,6 @@ export const syncHandlers = HttpApiBuilder.group(InstanceHttpApi, "sync", (handl
|
|||
)
|
||||
})
|
||||
|
||||
return handlers
|
||||
.handle("start", start)
|
||||
.handle("replay", replay)
|
||||
.handle("steal", steal)
|
||||
.handle("history", history)
|
||||
return handlers.handle("start", start).handle("replay", replay).handle("steal", steal).handle("history", history)
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -321,7 +321,6 @@ describe("workspace-old schemas and exports", () => {
|
|||
expect(() => WorkspaceOld.CreateInput.zod.parse({ ...input, id: "bad" })).toThrow()
|
||||
expect(() => WorkspaceOld.CreateInput.zod.parse({ ...input, branch: 1 })).toThrow()
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe("workspace-old CRUD", () => {
|
||||
|
|
|
|||
|
|
@ -5448,6 +5448,80 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/sync/steal": {
|
||||
"post": {
|
||||
"operationId": "sync.steal",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "directory",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "workspace",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"summary": "Steal session into workspace",
|
||||
"description": "Update a session to belong to the current workspace through the sync event system.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Session stolen into workspace",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sessionID": {
|
||||
"type": "string",
|
||||
"pattern": "^ses.*"
|
||||
}
|
||||
},
|
||||
"required": ["sessionID"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/BadRequestError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sessionID": {
|
||||
"type": "string",
|
||||
"pattern": "^ses.*"
|
||||
}
|
||||
},
|
||||
"required": ["sessionID"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-codeSamples": [
|
||||
{
|
||||
"lang": "js",
|
||||
"source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.sync.steal({\n ...\n})"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/sync/history": {
|
||||
"post": {
|
||||
"operationId": "sync.history.list",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue