Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions packages/app/src/components/dialog-confirm-delete.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Button } from "@opencode-ai/ui/button"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { Dialog } from "@opencode-ai/ui/dialog"

interface DialogConfirmDeleteProps {
title: string
description?: string
onConfirm: () => void | Promise<void>
}

export function DialogConfirmDelete(props: DialogConfirmDeleteProps) {
const dialog = useDialog()

async function handleConfirm() {
await props.onConfirm()
dialog.close()
}

return (
<Dialog title={props.title}>
<div class="flex flex-col gap-6 px-2.5 pb-3">
{props.description && <p class="text-14-regular text-text-base">{props.description}</p>}
<div class="flex justify-end gap-2">
<Button type="button" variant="ghost" size="large" onClick={() => dialog.close()}>
Cancel
</Button>
<Button
type="button"
variant="primary"
size="large"
class="bg-surface-critical-base hover:bg-surface-critical-base-hover"
onClick={handleConfirm}
>
Delete
</Button>
</div>
</div>
</Dialog>
)
}
39 changes: 38 additions & 1 deletion packages/app/src/components/session/session-header.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { createEffect, createMemo, createResource, Show } from "solid-js"
import { createEffect, createMemo, createResource, createSignal, Show } from "solid-js"
import { A, useNavigate, useParams } from "@solidjs/router"
import { useLayout } from "@/context/layout"
import { useCommand } from "@/context/command"
import { useServer } from "@/context/server"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { useSync } from "@/context/sync"
import { useGlobalSDK } from "@/context/global-sdk"
import { useSDK } from "@/context/sdk"
import { getFilename } from "@opencode-ai/util/path"
import { base64Decode, base64Encode } from "@opencode-ai/util/encode"
import { iife } from "@opencode-ai/util/iife"
Expand All @@ -17,12 +18,15 @@ import { Select } from "@opencode-ai/ui/select"
import { Popover } from "@opencode-ai/ui/popover"
import { TextField } from "@opencode-ai/ui/text-field"
import { DialogSelectServer } from "@/components/dialog-select-server"
import { DialogConfirmDelete } from "@/components/dialog-confirm-delete"
import { SessionLspIndicator } from "@/components/session-lsp-indicator"
import { SessionMcpIndicator } from "@/components/session-mcp-indicator"
import { showToast } from "@opencode-ai/ui/toast"
import type { Session } from "@opencode-ai/sdk/v2/client"

export function SessionHeader() {
const globalSDK = useGlobalSDK()
const sdk = useSDK()
const layout = useLayout()
const params = useParams()
const navigate = useNavigate()
Expand All @@ -37,6 +41,8 @@ export function SessionHeader() {
const currentSession = createMemo(() => sessions().find((s) => s.id === params.id))
const shareEnabled = createMemo(() => sync.data.config.share !== "disabled")

const [deleting, setDeleting] = createSignal(false)

function navigateToProject(directory: string) {
navigate(`/${base64Encode(directory)}`)
}
Expand All @@ -46,6 +52,34 @@ export function SessionHeader() {
navigate(`/${params.dir}/session/${session.id}`)
}

function deleteCurrentSession() {
const session = currentSession()
if (!session || deleting()) return

dialog.show(() => (
<DialogConfirmDelete
title="Delete session"
description={`Are you sure you want to delete "${session.title}"? This action cannot be undone.`}
onConfirm={async () => {
setDeleting(true)
try {
await sdk.client.session.delete({
sessionID: session.id,
directory: projectDirectory(),
})
showToast({ title: "Session deleted" })
navigate(`/${params.dir}/session`)
} catch (e) {
console.error("Failed to delete session", e)
showToast({ title: "Failed to delete session", description: String(e) })
} finally {
setDeleting(false)
}
}}
/>
))
}

return (
<header class="h-12 shrink-0 bg-background-base border-b border-border-weak-base flex" data-tauri-drag-region>
<button
Expand Down Expand Up @@ -92,6 +126,9 @@ export function SessionHeader() {
<TooltipKeybind class="hidden xl:block" title="New session" keybind={command.keybind("session.new")}>
<IconButton as={A} href={`/${params.dir}/session`} icon="edit-small-2" variant="ghost" />
</TooltipKeybind>
<Tooltip class="hidden xl:block" value="Delete session">
<IconButton icon="close" variant="ghost" onClick={deleteCurrentSession} disabled={deleting()} />
</Tooltip>
</Show>
</div>
<div class="flex items-center gap-3">
Expand Down
12 changes: 12 additions & 0 deletions packages/app/src/context/global-sync.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,18 @@ function createGlobalSync() {
bootstrapInstance(directory)
break
}
case "session.deleted": {
const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
if (result.found) {
setStore(
"session",
produce((draft) => {
draft.splice(result.index, 1)
}),
)
}
break
}
case "session.updated": {
const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
if (event.properties.info.time.archived) {
Expand Down