Add session rename dialogs and API wiring
This commit is contained in:
@@ -1,12 +1,14 @@
|
|||||||
import { Component, createSignal, Show, For, createEffect, onMount, onCleanup, createMemo } from "solid-js"
|
import { Component, createSignal, Show, For, createEffect, onMount, onCleanup, createMemo } from "solid-js"
|
||||||
import { Loader2, Trash2 } from "lucide-solid"
|
import { Loader2, Pencil, Trash2 } from "lucide-solid"
|
||||||
|
|
||||||
import type { Instance } from "../types/instance"
|
import type { Instance } from "../types/instance"
|
||||||
import { getParentSessions, createSession, setActiveParentSession, deleteSession, loading } from "../stores/sessions"
|
import { getParentSessions, createSession, setActiveParentSession, deleteSession, loading, renameSession } from "../stores/sessions"
|
||||||
import InstanceInfo from "./instance-info"
|
import InstanceInfo from "./instance-info"
|
||||||
import Kbd from "./kbd"
|
import Kbd from "./kbd"
|
||||||
|
import SessionRenameDialog from "./session-rename-dialog"
|
||||||
import { keyboardRegistry, type KeyboardShortcut } from "../lib/keyboard-registry"
|
import { keyboardRegistry, type KeyboardShortcut } from "../lib/keyboard-registry"
|
||||||
import { isMac } from "../lib/keyboard-utils"
|
import { isMac } from "../lib/keyboard-utils"
|
||||||
|
import { showToastNotification } from "../lib/notifications"
|
||||||
import { getLogger } from "../lib/logger"
|
import { getLogger } from "../lib/logger"
|
||||||
const log = getLogger("actions")
|
const log = getLogger("actions")
|
||||||
|
|
||||||
@@ -24,6 +26,8 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
|
|||||||
const [isDesktopLayout, setIsDesktopLayout] = createSignal(
|
const [isDesktopLayout, setIsDesktopLayout] = createSignal(
|
||||||
typeof window !== "undefined" ? window.matchMedia("(min-width: 1024px)").matches : false,
|
typeof window !== "undefined" ? window.matchMedia("(min-width: 1024px)").matches : false,
|
||||||
)
|
)
|
||||||
|
const [renameTarget, setRenameTarget] = createSignal<{ id: string; title: string; label: string } | null>(null)
|
||||||
|
const [isRenaming, setIsRenaming] = createSignal(false)
|
||||||
|
|
||||||
const parentSessions = () => getParentSessions(props.instance.id)
|
const parentSessions = () => getParentSessions(props.instance.id)
|
||||||
const isFetchingSessions = createMemo(() => Boolean(loading().fetchingSessions.get(props.instance.id)))
|
const isFetchingSessions = createMemo(() => Boolean(loading().fetchingSessions.get(props.instance.id)))
|
||||||
@@ -74,6 +78,25 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleKeyDown(e: KeyboardEvent) {
|
function handleKeyDown(e: KeyboardEvent) {
|
||||||
|
let activeElement: HTMLElement | null = null
|
||||||
|
if (typeof document !== "undefined") {
|
||||||
|
activeElement = document.activeElement as HTMLElement | null
|
||||||
|
}
|
||||||
|
const insideModal = activeElement?.closest(".modal-surface") || activeElement?.closest("[role='dialog']")
|
||||||
|
const isEditingField =
|
||||||
|
activeElement &&
|
||||||
|
(["INPUT", "TEXTAREA", "SELECT"].includes(activeElement.tagName) ||
|
||||||
|
activeElement.isContentEditable ||
|
||||||
|
Boolean(insideModal))
|
||||||
|
|
||||||
|
if (isEditingField) {
|
||||||
|
if (insideModal && e.key === "Escape" && renameTarget()) {
|
||||||
|
e.preventDefault()
|
||||||
|
closeRenameDialog()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (showInstanceInfoOverlay()) {
|
if (showInstanceInfoOverlay()) {
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -92,42 +115,56 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
|
|||||||
|
|
||||||
if (sessions.length === 0) return
|
if (sessions.length === 0) return
|
||||||
|
|
||||||
|
const listFocused = focusMode() === "sessions"
|
||||||
|
|
||||||
if (e.key === "ArrowDown") {
|
if (e.key === "ArrowDown") {
|
||||||
|
if (!listFocused) {
|
||||||
|
setFocusMode("sessions")
|
||||||
|
setSelectedIndex(0)
|
||||||
|
}
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
const newIndex = Math.min(selectedIndex() + 1, sessions.length - 1)
|
const newIndex = Math.min(selectedIndex() + 1, sessions.length - 1)
|
||||||
setSelectedIndex(newIndex)
|
setSelectedIndex(newIndex)
|
||||||
setFocusMode("sessions")
|
|
||||||
scrollToIndex(newIndex)
|
scrollToIndex(newIndex)
|
||||||
} else if (e.key === "ArrowUp") {
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key === "ArrowUp") {
|
||||||
|
if (!listFocused) {
|
||||||
|
setFocusMode("sessions")
|
||||||
|
setSelectedIndex(Math.max(parentSessions().length - 1, 0))
|
||||||
|
}
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
const newIndex = Math.max(selectedIndex() - 1, 0)
|
const newIndex = Math.max(selectedIndex() - 1, 0)
|
||||||
setSelectedIndex(newIndex)
|
setSelectedIndex(newIndex)
|
||||||
setFocusMode("sessions")
|
|
||||||
scrollToIndex(newIndex)
|
scrollToIndex(newIndex)
|
||||||
} else if (e.key === "PageDown") {
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!listFocused) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key === "PageDown") {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
const pageSize = 5
|
const pageSize = 5
|
||||||
const newIndex = Math.min(selectedIndex() + pageSize, sessions.length - 1)
|
const newIndex = Math.min(selectedIndex() + pageSize, sessions.length - 1)
|
||||||
setSelectedIndex(newIndex)
|
setSelectedIndex(newIndex)
|
||||||
setFocusMode("sessions")
|
|
||||||
scrollToIndex(newIndex)
|
scrollToIndex(newIndex)
|
||||||
} else if (e.key === "PageUp") {
|
} else if (e.key === "PageUp") {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
const pageSize = 5
|
const pageSize = 5
|
||||||
const newIndex = Math.max(selectedIndex() - pageSize, 0)
|
const newIndex = Math.max(selectedIndex() - pageSize, 0)
|
||||||
setSelectedIndex(newIndex)
|
setSelectedIndex(newIndex)
|
||||||
setFocusMode("sessions")
|
|
||||||
scrollToIndex(newIndex)
|
scrollToIndex(newIndex)
|
||||||
} else if (e.key === "Home") {
|
} else if (e.key === "Home") {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
setSelectedIndex(0)
|
setSelectedIndex(0)
|
||||||
setFocusMode("sessions")
|
|
||||||
scrollToIndex(0)
|
scrollToIndex(0)
|
||||||
} else if (e.key === "End") {
|
} else if (e.key === "End") {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
const newIndex = sessions.length - 1
|
const newIndex = sessions.length - 1
|
||||||
setSelectedIndex(newIndex)
|
setSelectedIndex(newIndex)
|
||||||
setFocusMode("sessions")
|
|
||||||
scrollToIndex(newIndex)
|
scrollToIndex(newIndex)
|
||||||
} else if (e.key === "Enter") {
|
} else if (e.key === "Enter") {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -138,6 +175,7 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function handleEnterKey() {
|
async function handleEnterKey() {
|
||||||
const sessions = parentSessions()
|
const sessions = parentSessions()
|
||||||
const index = selectedIndex()
|
const index = selectedIndex()
|
||||||
@@ -234,6 +272,31 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openRenameDialogForSession(sessionId: string, title: string) {
|
||||||
|
const label = title && title.trim() ? title : sessionId
|
||||||
|
setRenameTarget({ id: sessionId, title: title ?? "", label })
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeRenameDialog() {
|
||||||
|
setRenameTarget(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRenameSubmit(nextTitle: string) {
|
||||||
|
const target = renameTarget()
|
||||||
|
if (!target) return
|
||||||
|
|
||||||
|
setIsRenaming(true)
|
||||||
|
try {
|
||||||
|
await renameSession(props.instance.id, target.id, nextTitle)
|
||||||
|
setRenameTarget(null)
|
||||||
|
} catch (error) {
|
||||||
|
log.error("Failed to rename session:", error)
|
||||||
|
showToastNotification({ message: "Unable to rename session", variant: "error" })
|
||||||
|
} finally {
|
||||||
|
setIsRenaming(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleNewSession() {
|
async function handleNewSession() {
|
||||||
if (isCreating()) return
|
if (isCreating()) return
|
||||||
|
|
||||||
@@ -355,6 +418,18 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
|
|||||||
<Show when={isFocused()}>
|
<Show when={isFocused()}>
|
||||||
<div class="flex items-center gap-2 flex-shrink-0">
|
<div class="flex items-center gap-2 flex-shrink-0">
|
||||||
<kbd class="kbd flex-shrink-0">↵</kbd>
|
<kbd class="kbd flex-shrink-0">↵</kbd>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="p-1.5 rounded transition-colors text-muted hover:text-primary focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
|
||||||
|
title="Rename session"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
openRenameDialogForSession(session.id, session.title || "")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Pencil class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="p-1.5 rounded transition-colors text-muted hover:text-red-500 dark:hover:text-red-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
|
class="p-1.5 rounded transition-colors text-muted hover:text-red-500 dark:hover:text-red-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
|
||||||
@@ -488,10 +563,17 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<SessionRenameDialog
|
||||||
|
open={Boolean(renameTarget())}
|
||||||
|
currentTitle={renameTarget()?.title ?? ""}
|
||||||
|
sessionLabel={renameTarget()?.label}
|
||||||
|
isSubmitting={isRenaming()}
|
||||||
|
onRename={handleRenameSubmit}
|
||||||
|
onClose={closeRenameDialog}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default InstanceWelcomeView
|
export default InstanceWelcomeView
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { Component, For, Show, createSignal, createEffect, onCleanup, onMount, createMemo, JSX } from "solid-js"
|
import { Component, For, Show, createSignal, createEffect, onCleanup, onMount, createMemo, JSX } from "solid-js"
|
||||||
import type { Session, SessionStatus } from "../types/session"
|
import type { Session, SessionStatus } from "../types/session"
|
||||||
import { getSessionStatus } from "../stores/session-status"
|
import { getSessionStatus } from "../stores/session-status"
|
||||||
import { MessageSquare, Info, X, Copy, Trash2 } from "lucide-solid"
|
import { MessageSquare, Info, X, Copy, Trash2, Pencil } from "lucide-solid"
|
||||||
import KeyboardHint from "./keyboard-hint"
|
import KeyboardHint from "./keyboard-hint"
|
||||||
import Kbd from "./kbd"
|
import Kbd from "./kbd"
|
||||||
|
import SessionRenameDialog from "./session-rename-dialog"
|
||||||
import { keyboardRegistry } from "../lib/keyboard-registry"
|
import { keyboardRegistry } from "../lib/keyboard-registry"
|
||||||
import { formatShortcut } from "../lib/keyboard-utils"
|
import { formatShortcut } from "../lib/keyboard-utils"
|
||||||
import { showToastNotification } from "../lib/notifications"
|
import { showToastNotification } from "../lib/notifications"
|
||||||
import { deleteSession, loading } from "../stores/sessions"
|
import { deleteSession, loading, renameSession } from "../stores/sessions"
|
||||||
import { getLogger } from "../lib/logger"
|
import { getLogger } from "../lib/logger"
|
||||||
const log = getLogger("session")
|
const log = getLogger("session")
|
||||||
|
|
||||||
@@ -66,6 +67,8 @@ const SessionList: Component<SessionListProps> = (props) => {
|
|||||||
const [isResizing, setIsResizing] = createSignal(false)
|
const [isResizing, setIsResizing] = createSignal(false)
|
||||||
const [startX, setStartX] = createSignal(0)
|
const [startX, setStartX] = createSignal(0)
|
||||||
const [startWidth, setStartWidth] = createSignal(DEFAULT_WIDTH)
|
const [startWidth, setStartWidth] = createSignal(DEFAULT_WIDTH)
|
||||||
|
const [renameTarget, setRenameTarget] = createSignal<{ id: string; title: string; label: string } | null>(null)
|
||||||
|
const [isRenaming, setIsRenaming] = createSignal(false)
|
||||||
const infoShortcut = keyboardRegistry.get("switch-to-info")
|
const infoShortcut = keyboardRegistry.get("switch-to-info")
|
||||||
|
|
||||||
const isSessionDeleting = (sessionId: string) => {
|
const isSessionDeleting = (sessionId: string) => {
|
||||||
@@ -133,10 +136,38 @@ const SessionList: Component<SessionListProps> = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const openRenameDialog = (sessionId: string) => {
|
||||||
|
const session = props.sessions.get(sessionId)
|
||||||
|
if (!session) return
|
||||||
|
const label = session.title && session.title.trim() ? session.title : sessionId
|
||||||
|
setRenameTarget({ id: sessionId, title: session.title ?? "", label })
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeRenameDialog = () => {
|
||||||
|
setRenameTarget(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRenameSubmit = async (nextTitle: string) => {
|
||||||
|
const target = renameTarget()
|
||||||
|
if (!target) return
|
||||||
|
|
||||||
|
setIsRenaming(true)
|
||||||
|
try {
|
||||||
|
await renameSession(props.instanceId, target.id, nextTitle)
|
||||||
|
setRenameTarget(null)
|
||||||
|
} catch (error) {
|
||||||
|
log.error(`Failed to rename session ${target.id}:`, error)
|
||||||
|
showToastNotification({ message: "Unable to rename session", variant: "error" })
|
||||||
|
} finally {
|
||||||
|
setIsRenaming(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const clampWidth = (width: number) => Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, width))
|
const clampWidth = (width: number) => Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, width))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const removeMouseListeners = () => {
|
const removeMouseListeners = () => {
|
||||||
if (mouseMoveHandler) {
|
if (mouseMoveHandler) {
|
||||||
document.removeEventListener("mousemove", mouseMoveHandler)
|
document.removeEventListener("mousemove", mouseMoveHandler)
|
||||||
@@ -281,6 +312,19 @@ const SessionList: Component<SessionListProps> = (props) => {
|
|||||||
>
|
>
|
||||||
<Copy class="w-3 h-3" />
|
<Copy class="w-3 h-3" />
|
||||||
</span>
|
</span>
|
||||||
|
<span
|
||||||
|
class={`session-item-close opacity-80 hover:opacity-100 ${isActive() ? "hover:bg-white/20" : "hover:bg-surface-hover"}`}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation()
|
||||||
|
openRenameDialog(rowProps.sessionId)
|
||||||
|
}}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label="Rename session"
|
||||||
|
title="Rename session"
|
||||||
|
>
|
||||||
|
<Pencil class="w-3 h-3" />
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
class={`session-item-close opacity-80 hover:opacity-100 ${isActive() ? "hover:bg-white/20" : "hover:bg-surface-hover"}`}
|
class={`session-item-close opacity-80 hover:opacity-100 ${isActive() ? "hover:bg-white/20" : "hover:bg-surface-hover"}`}
|
||||||
onClick={(event) => handleDeleteSession(event, rowProps.sessionId)}
|
onClick={(event) => handleDeleteSession(event, rowProps.sessionId)}
|
||||||
@@ -418,8 +462,18 @@ const SessionList: Component<SessionListProps> = (props) => {
|
|||||||
{props.footerContent ?? null}
|
{props.footerContent ?? null}
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
|
<SessionRenameDialog
|
||||||
|
open={Boolean(renameTarget())}
|
||||||
|
currentTitle={renameTarget()?.title ?? ""}
|
||||||
|
sessionLabel={renameTarget()?.label}
|
||||||
|
isSubmitting={isRenaming()}
|
||||||
|
onRename={handleRenameSubmit}
|
||||||
|
onClose={closeRenameDialog}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SessionList
|
export default SessionList
|
||||||
|
|
||||||
|
|||||||
130
packages/ui/src/components/session-rename-dialog.tsx
Normal file
130
packages/ui/src/components/session-rename-dialog.tsx
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import { Dialog } from "@kobalte/core/dialog"
|
||||||
|
import { Component, Show, createEffect, createSignal } from "solid-js"
|
||||||
|
|
||||||
|
interface SessionRenameDialogProps {
|
||||||
|
open: boolean
|
||||||
|
currentTitle: string
|
||||||
|
sessionLabel?: string
|
||||||
|
isSubmitting?: boolean
|
||||||
|
onRename: (nextTitle: string) => Promise<void> | void
|
||||||
|
onClose: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const SessionRenameDialog: Component<SessionRenameDialogProps> = (props) => {
|
||||||
|
const [title, setTitle] = createSignal("")
|
||||||
|
const inputId = `session-rename-${Math.random().toString(36).slice(2)}`
|
||||||
|
let inputRef: HTMLInputElement | undefined
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!props.open) return
|
||||||
|
setTitle(props.currentTitle ?? "")
|
||||||
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!props.open) return
|
||||||
|
if (typeof window === "undefined" || typeof window.requestAnimationFrame !== "function") return
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
inputRef?.focus()
|
||||||
|
inputRef?.select()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const isSubmitting = () => Boolean(props.isSubmitting)
|
||||||
|
const isRenameDisabled = () => isSubmitting() || !title().trim()
|
||||||
|
|
||||||
|
async function handleRename(event?: Event) {
|
||||||
|
event?.preventDefault()
|
||||||
|
if (isRenameDisabled()) return
|
||||||
|
await props.onRename(title().trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
const description = () => {
|
||||||
|
if (props.sessionLabel && props.sessionLabel.trim()) {
|
||||||
|
return `Update the title for "${props.sessionLabel}".`
|
||||||
|
}
|
||||||
|
return "Set a new title for this session."
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={props.open}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open && !isSubmitting()) {
|
||||||
|
props.onClose()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Dialog.Portal>
|
||||||
|
<Dialog.Overlay class="modal-overlay" />
|
||||||
|
<div class="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||||
|
<Dialog.Content class="modal-surface w-full max-w-sm p-6" tabIndex={-1}>
|
||||||
|
<Dialog.Title class="text-lg font-semibold text-primary">Rename Session</Dialog.Title>
|
||||||
|
<Dialog.Description class="text-sm text-secondary mt-1">
|
||||||
|
{description()}
|
||||||
|
</Dialog.Description>
|
||||||
|
|
||||||
|
<form class="mt-4 space-y-4" onSubmit={handleRename}>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label class="text-sm font-medium text-secondary" for={inputId}>
|
||||||
|
Session name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id={inputId}
|
||||||
|
ref={(element) => {
|
||||||
|
inputRef = element
|
||||||
|
}}
|
||||||
|
type="text"
|
||||||
|
value={title()}
|
||||||
|
onInput={(event) => setTitle(event.currentTarget.value)}
|
||||||
|
placeholder="Enter a session name"
|
||||||
|
class="w-full px-3 py-2 text-sm bg-surface-base border border-base rounded text-primary focus-ring-accent"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-end gap-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="button-tertiary"
|
||||||
|
onClick={() => {
|
||||||
|
if (!isSubmitting()) {
|
||||||
|
props.onClose()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={isSubmitting()}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="button-primary flex items-center gap-2 disabled:opacity-60 disabled:cursor-not-allowed"
|
||||||
|
disabled={isRenameDisabled()}
|
||||||
|
>
|
||||||
|
<Show
|
||||||
|
when={!isSubmitting()}
|
||||||
|
fallback={
|
||||||
|
<>
|
||||||
|
<svg class="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
||||||
|
<path
|
||||||
|
class="opacity-75"
|
||||||
|
fill="currentColor"
|
||||||
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>Renaming…</span>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Rename
|
||||||
|
</Show>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Dialog.Content>
|
||||||
|
</div>
|
||||||
|
</Dialog.Portal>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SessionRenameDialog
|
||||||
@@ -334,9 +334,39 @@ async function updateSessionModel(
|
|||||||
updateSessionInfo(instanceId, sessionId)
|
updateSessionInfo(instanceId, sessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function renameSession(instanceId: string, sessionId: string, nextTitle: string): Promise<void> {
|
||||||
|
const instance = instances().get(instanceId)
|
||||||
|
if (!instance || !instance.client) {
|
||||||
|
throw new Error("Instance not ready")
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = sessions().get(instanceId)?.get(sessionId)
|
||||||
|
if (!session) {
|
||||||
|
throw new Error("Session not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
const trimmedTitle = nextTitle.trim()
|
||||||
|
if (!trimmedTitle) {
|
||||||
|
throw new Error("Session title is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
await instance.client.session.update({
|
||||||
|
path: { id: sessionId },
|
||||||
|
body: { title: trimmedTitle },
|
||||||
|
})
|
||||||
|
|
||||||
|
withSession(instanceId, sessionId, (current) => {
|
||||||
|
current.title = trimmedTitle
|
||||||
|
const time = { ...(current.time ?? {}) }
|
||||||
|
time.updated = Date.now()
|
||||||
|
current.time = time
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
abortSession,
|
abortSession,
|
||||||
executeCustomCommand,
|
executeCustomCommand,
|
||||||
|
renameSession,
|
||||||
runShellCommand,
|
runShellCommand,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
updateSessionAgent,
|
updateSessionAgent,
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
abortSession,
|
abortSession,
|
||||||
executeCustomCommand,
|
executeCustomCommand,
|
||||||
|
renameSession,
|
||||||
runShellCommand,
|
runShellCommand,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
updateSessionAgent,
|
updateSessionAgent,
|
||||||
@@ -82,6 +83,7 @@ export {
|
|||||||
createSession,
|
createSession,
|
||||||
deleteSession,
|
deleteSession,
|
||||||
executeCustomCommand,
|
executeCustomCommand,
|
||||||
|
renameSession,
|
||||||
runShellCommand,
|
runShellCommand,
|
||||||
fetchAgents,
|
fetchAgents,
|
||||||
fetchProviders,
|
fetchProviders,
|
||||||
|
|||||||
Reference in New Issue
Block a user