feat: better icons and overlay panes
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import { FileText, FolderOpen, ImageIcon, Link, X } from 'lucide-react'
|
||||
|
||||
import { FileText, FolderOpen, ImageIcon, Link, X } from '@/lib/icons'
|
||||
import type { ComposerAttachment } from '@/store/composer'
|
||||
|
||||
export function AttachmentList({
|
||||
@@ -22,10 +21,7 @@ function AttachmentPill({ attachment, onRemove }: { attachment: ComposerAttachme
|
||||
const Icon = { folder: FolderOpen, url: Link, image: ImageIcon, file: FileText }[attachment.kind]
|
||||
|
||||
return (
|
||||
<div
|
||||
className="group/attachment relative shrink-0"
|
||||
title={attachment.label}
|
||||
>
|
||||
<div className="group/attachment relative shrink-0" title={attachment.label}>
|
||||
{attachment.previewUrl && attachment.kind === 'image' ? (
|
||||
<img
|
||||
alt={attachment.label}
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
import {
|
||||
Clipboard,
|
||||
FileText,
|
||||
FolderOpen,
|
||||
ImageIcon,
|
||||
Link,
|
||||
type LucideIcon,
|
||||
MessageSquareText,
|
||||
Plus
|
||||
} from 'lucide-react'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -21,6 +10,7 @@ import {
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { Clipboard, FileText, FolderOpen, ImageIcon, Link, type LucideIcon, MessageSquareText, Plus } from '@/lib/icons'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
import { GHOST_ICON_BTN } from './controls'
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ArrowUp, AudioLines, Loader2, Mic, MicOff, Square } from 'lucide-react'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { triggerHaptic } from '@/lib/haptics'
|
||||
import { ArrowUp, AudioLines, Loader2, Mic, MicOff, Square } from '@/lib/icons'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
import type { ConversationStatus } from './hooks/use-voice-conversation'
|
||||
|
||||
@@ -3,10 +3,10 @@ import { useCallback } from 'react'
|
||||
|
||||
import type { HermesGateway } from '@/hermes'
|
||||
import {
|
||||
type CommandsCatalogLike,
|
||||
desktopSlashDescription,
|
||||
filterDesktopCommandsCatalog,
|
||||
isDesktopSlashSuggestion,
|
||||
type CommandsCatalogLike
|
||||
isDesktopSlashSuggestion
|
||||
} from '@/lib/desktop-slash-commands'
|
||||
|
||||
import type { CompletionEntry, CompletionPayload } from './use-live-completion-adapter'
|
||||
@@ -57,17 +57,17 @@ export function useSlashCompletions(options: { gateway: HermesGateway | null }):
|
||||
if (!query) {
|
||||
const catalog = filterDesktopCommandsCatalog(await gateway.request<CommandsCatalogLike>('commands.catalog'))
|
||||
|
||||
const items = (catalog.pairs ?? [])
|
||||
.map(([command, meta]) => ({
|
||||
text: command,
|
||||
display: command,
|
||||
meta
|
||||
}))
|
||||
const items = (catalog.pairs ?? []).map(([command, meta]) => ({
|
||||
text: command,
|
||||
display: command,
|
||||
meta
|
||||
}))
|
||||
|
||||
return { items, query }
|
||||
}
|
||||
|
||||
const result = await gateway.request<{ items?: CompletionEntry[] }>('complete.slash', { text })
|
||||
|
||||
const items = (result.items ?? [])
|
||||
.filter(item => isDesktopSlashSuggestion(item.text))
|
||||
.map(item => ({
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Globe } from 'lucide-react'
|
||||
import type * as React from 'react'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
@@ -11,6 +10,7 @@ import {
|
||||
DialogTitle
|
||||
} from '@/components/ui/dialog'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Globe } from '@/lib/icons'
|
||||
|
||||
const URL_HINT = /^https?:\/\//i
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useStore } from '@nanostores/react'
|
||||
import { Loader2, Mic, Volume2, VolumeX } from 'lucide-react'
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Loader2, Mic, Volume2, VolumeX } from '@/lib/icons'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { stopVoicePlayback } from '@/lib/voice-playback'
|
||||
import { $voicePlayback } from '@/store/voice-playback'
|
||||
|
||||
@@ -48,12 +48,17 @@ export function extractDroppedFiles(transfer: DataTransfer): DroppedFile[] {
|
||||
const getPath = window.hermesDesktop?.getPathForFile
|
||||
|
||||
const fileList = transfer.files
|
||||
|
||||
if (fileList) {
|
||||
for (let i = 0; i < fileList.length; i += 1) {
|
||||
const file = fileList.item(i)
|
||||
if (!file || seen.has(file)) continue
|
||||
|
||||
if (!file || seen.has(file)) {
|
||||
continue
|
||||
}
|
||||
seen.add(file)
|
||||
let path = ''
|
||||
|
||||
if (getPath) {
|
||||
try {
|
||||
path = getPath(file) || ''
|
||||
@@ -61,19 +66,28 @@ export function extractDroppedFiles(transfer: DataTransfer): DroppedFile[] {
|
||||
path = ''
|
||||
}
|
||||
}
|
||||
|
||||
result.push({ file, path })
|
||||
}
|
||||
}
|
||||
|
||||
const items = transfer.items
|
||||
|
||||
if (items) {
|
||||
for (let i = 0; i < items.length; i += 1) {
|
||||
const item = items[i]
|
||||
if (!item || item.kind !== 'file') continue
|
||||
|
||||
if (!item || item.kind !== 'file') {
|
||||
continue
|
||||
}
|
||||
const file = item.getAsFile()
|
||||
if (!file || seen.has(file)) continue
|
||||
|
||||
if (!file || seen.has(file)) {
|
||||
continue
|
||||
}
|
||||
seen.add(file)
|
||||
let path = ''
|
||||
|
||||
if (getPath) {
|
||||
try {
|
||||
path = getPath(file) || ''
|
||||
@@ -81,6 +95,7 @@ export function extractDroppedFiles(transfer: DataTransfer): DroppedFile[] {
|
||||
path = ''
|
||||
}
|
||||
}
|
||||
|
||||
result.push({ file, path })
|
||||
}
|
||||
}
|
||||
@@ -94,11 +109,7 @@ interface ComposerActionsOptions {
|
||||
requestGateway: <T>(method: string, params?: Record<string, unknown>) => Promise<T>
|
||||
}
|
||||
|
||||
export function useComposerActions({
|
||||
activeSessionId,
|
||||
currentCwd,
|
||||
requestGateway
|
||||
}: ComposerActionsOptions) {
|
||||
export function useComposerActions({ activeSessionId, currentCwd, requestGateway }: ComposerActionsOptions) {
|
||||
const addContextRefAttachment = useCallback((refText: string, label?: string, detail?: string) => {
|
||||
let kind: ComposerAttachment['kind'] = 'file'
|
||||
|
||||
@@ -169,38 +180,35 @@ export function useComposerActions({
|
||||
[currentCwd]
|
||||
)
|
||||
|
||||
const attachImagePath = useCallback(
|
||||
async (filePath: string) => {
|
||||
if (!filePath) {
|
||||
return false
|
||||
const attachImagePath = useCallback(async (filePath: string) => {
|
||||
if (!filePath) {
|
||||
return false
|
||||
}
|
||||
|
||||
const baseAttachment: ComposerAttachment = {
|
||||
id: attachmentId('image', filePath),
|
||||
kind: 'image',
|
||||
label: pathLabel(filePath),
|
||||
detail: filePath,
|
||||
path: filePath
|
||||
}
|
||||
|
||||
addComposerAttachment(baseAttachment)
|
||||
|
||||
try {
|
||||
const previewUrl = await window.hermesDesktop?.readFileDataUrl(filePath)
|
||||
|
||||
if (previewUrl) {
|
||||
addComposerAttachment({ ...baseAttachment, previewUrl })
|
||||
}
|
||||
|
||||
const baseAttachment: ComposerAttachment = {
|
||||
id: attachmentId('image', filePath),
|
||||
kind: 'image',
|
||||
label: pathLabel(filePath),
|
||||
detail: filePath,
|
||||
path: filePath
|
||||
}
|
||||
return true
|
||||
} catch (err) {
|
||||
notifyError(err, 'Image preview failed')
|
||||
|
||||
addComposerAttachment(baseAttachment)
|
||||
|
||||
try {
|
||||
const previewUrl = await window.hermesDesktop?.readFileDataUrl(filePath)
|
||||
|
||||
if (previewUrl) {
|
||||
addComposerAttachment({ ...baseAttachment, previewUrl })
|
||||
}
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
notifyError(err, 'Image preview failed')
|
||||
|
||||
return true
|
||||
}
|
||||
},
|
||||
[]
|
||||
)
|
||||
return true
|
||||
}
|
||||
}, [])
|
||||
|
||||
const attachImageBlob = useCallback(
|
||||
async (blob: Blob) => {
|
||||
@@ -284,22 +292,26 @@ export function useComposerActions({
|
||||
let lastFailure: string | null = null
|
||||
|
||||
for (const { file, path: knownPath } of candidates) {
|
||||
const fallbackPath = !knownPath && window.hermesDesktop?.getPathForFile ? window.hermesDesktop.getPathForFile(file) : ''
|
||||
const fallbackPath =
|
||||
!knownPath && window.hermesDesktop?.getPathForFile ? window.hermesDesktop.getPathForFile(file) : ''
|
||||
const filePath = knownPath || fallbackPath || ''
|
||||
const isImage = file.type.startsWith('image/') || isImagePath(file.name) || (filePath && isImagePath(filePath))
|
||||
|
||||
if (isImage) {
|
||||
if ((filePath && (await attachImagePath(filePath))) || (await attachImageBlob(file))) {
|
||||
attached = true
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
lastFailure = `Could not attach ${file.name || 'image'}`
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if (filePath && attachContextFilePath(filePath)) {
|
||||
attached = true
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
} from '@assistant-ui/react'
|
||||
import { useStore } from '@nanostores/react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { ChevronDown } from 'lucide-react'
|
||||
import type * as React from 'react'
|
||||
import { Suspense, useMemo, useRef } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
@@ -18,6 +17,7 @@ import { Button } from '@/components/ui/button'
|
||||
import { getGlobalModelOptions, type HermesGateway } from '@/hermes'
|
||||
import type { ChatMessage } from '@/lib/chat-messages'
|
||||
import { quickModelOptions, sessionTitle, toRuntimeMessage } from '@/lib/chat-runtime'
|
||||
import { ChevronDown } from '@/lib/icons'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { $pinnedSessionIds } from '@/store/layout'
|
||||
import {
|
||||
@@ -264,7 +264,12 @@ export function ChatView({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={cn('relative col-start-2 col-end-3 row-start-1 flex h-[calc(100vh-0.375rem)] min-w-0 flex-col overflow-hidden rounded-[0.9375rem] bg-transparent', className)}>
|
||||
<div
|
||||
className={cn(
|
||||
'relative col-start-2 col-end-3 row-start-1 flex h-[calc(100vh-0.375rem)] min-w-0 flex-col overflow-hidden rounded-[0.9375rem] bg-transparent',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<header className={cn(titlebarHeaderBaseClass, isRoutedSessionView && titlebarHeaderShadowClass)}>
|
||||
<div className="min-w-0 flex-1">
|
||||
{title && (
|
||||
|
||||
@@ -99,9 +99,7 @@ export function ChatPreviewRail({
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="pointer-events-none col-start-3 col-end-4 row-start-1 min-w-0 overflow-hidden"
|
||||
>
|
||||
<div className="pointer-events-none col-start-3 col-end-4 row-start-1 min-w-0 overflow-hidden">
|
||||
<PreviewPane
|
||||
onRestartServer={onRestartServer}
|
||||
reloadRequest={previewReloadRequest}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useStore } from '@nanostores/react'
|
||||
import { Bug, Check, Copy, PanelBottom, RefreshCw, Send, Trash2, X } from 'lucide-react'
|
||||
import type { CSSProperties, PointerEvent as ReactPointerEvent } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
|
||||
import type { SetTitlebarToolGroup, TitlebarTool } from '@/app/shell/titlebar-controls'
|
||||
import { Bug, Check, Copy, PanelBottom, RefreshCw, Send, Trash2, X } from '@/lib/icons'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { $composerDraft, setComposerDraft } from '@/store/composer'
|
||||
import { notify, notifyError } from '@/store/notifications'
|
||||
@@ -265,12 +265,7 @@ async function writeClipboardText(text: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export function PreviewPane({
|
||||
onRestartServer,
|
||||
reloadRequest = 0,
|
||||
setTitlebarToolGroup,
|
||||
target
|
||||
}: PreviewPaneProps) {
|
||||
export function PreviewPane({ onRestartServer, reloadRequest = 0, setTitlebarToolGroup, target }: PreviewPaneProps) {
|
||||
const consoleBodyRef = useRef<HTMLDivElement | null>(null)
|
||||
const consoleShouldStickRef = useRef(true)
|
||||
const hostRef = useRef<HTMLDivElement | null>(null)
|
||||
@@ -295,6 +290,7 @@ export function PreviewPane({
|
||||
|
||||
const previewLabel =
|
||||
target.label && target.label.replace(/\/$/, '') !== currentLabel.replace(/\/$/, '') ? target.label : currentLabel
|
||||
|
||||
const restartingServer =
|
||||
previewServerRestart?.status === 'running' &&
|
||||
(previewServerRestart.url === target.url || previewServerRestart.url === currentUrl)
|
||||
@@ -532,10 +528,10 @@ export function PreviewPane({
|
||||
previewServerRestart.status === 'running'
|
||||
? previewServerRestart.message
|
||||
: previewServerRestart.status === 'complete'
|
||||
? `Hermes finished restarting the preview server${
|
||||
previewServerRestart.message ? `: ${previewServerRestart.message}` : ''
|
||||
}`
|
||||
: `Server restart failed: ${previewServerRestart.message || 'unknown error'}`
|
||||
? `Hermes finished restarting the preview server${
|
||||
previewServerRestart.message ? `: ${previewServerRestart.message}` : ''
|
||||
}`
|
||||
: `Server restart failed: ${previewServerRestart.message || 'unknown error'}`
|
||||
})
|
||||
|
||||
if (previewServerRestart.status === 'complete') {
|
||||
@@ -549,6 +545,7 @@ export function PreviewPane({
|
||||
}
|
||||
|
||||
const taskId = previewServerRestart.taskId
|
||||
|
||||
const timer = window.setTimeout(() => {
|
||||
failPreviewServerRestart(
|
||||
taskId,
|
||||
@@ -578,7 +575,11 @@ export function PreviewPane({
|
||||
}, [appendConsoleEntry, reloadPreview, reloadRequest, target.kind])
|
||||
|
||||
useEffect(() => {
|
||||
if (target.kind !== 'file' || !window.hermesDesktop?.watchPreviewFile || !window.hermesDesktop?.onPreviewFileChanged) {
|
||||
if (
|
||||
target.kind !== 'file' ||
|
||||
!window.hermesDesktop?.watchPreviewFile ||
|
||||
!window.hermesDesktop?.onPreviewFileChanged
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -688,6 +689,7 @@ export function PreviewPane({
|
||||
message?: string
|
||||
sourceId?: string
|
||||
}
|
||||
|
||||
const message = detail.message || ''
|
||||
|
||||
appendConsoleEntry({
|
||||
@@ -783,7 +785,10 @@ export function PreviewPane({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pointer-events-auto relative min-h-0 flex-1 overflow-hidden bg-background" ref={previewContentRef}>
|
||||
<div
|
||||
className="pointer-events-auto relative min-h-0 flex-1 overflow-hidden bg-background"
|
||||
ref={previewContentRef}
|
||||
>
|
||||
<div
|
||||
className={cn('absolute inset-0 flex bg-background', loadError && 'pointer-events-none opacity-0')}
|
||||
ref={hostRef}
|
||||
@@ -843,7 +848,9 @@ export function PreviewPane({
|
||||
onClick={async () => {
|
||||
await copyConsoleText(
|
||||
sendableLogs,
|
||||
visibleSelection.length > 0 ? `${visibleSelection.length} selected entries` : 'All console entries'
|
||||
visibleSelection.length > 0
|
||||
? `${visibleSelection.length} selected entries`
|
||||
: 'All console entries'
|
||||
)
|
||||
setCopiedAll(true)
|
||||
setTimeout(() => setCopiedAll(false), 1500)
|
||||
@@ -869,7 +876,10 @@ export function PreviewPane({
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto px-2 py-1.5 font-mono text-[0.6875rem] leading-relaxed" ref={consoleBodyRef}>
|
||||
<div
|
||||
className="min-h-0 flex-1 overflow-y-auto px-2 py-1.5 font-mono text-[0.6875rem] leading-relaxed"
|
||||
ref={consoleBodyRef}
|
||||
>
|
||||
{logs.length > 0 ? (
|
||||
logs.map(log => {
|
||||
const selected = selectedLogIds.has(log.id)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import { FolderOpen, GitBranch, Pencil } from 'lucide-react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { FolderOpen, GitBranch, Pencil } from '@/lib/icons'
|
||||
|
||||
import { RailSection } from './rail-section'
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { ChevronDown } from 'lucide-react'
|
||||
|
||||
import { ChevronDown } from '@/lib/icons'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface RailActionRowProps {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
'use client'
|
||||
|
||||
import { ChevronDown } from 'lucide-react'
|
||||
import { type ReactNode, useState } from 'react'
|
||||
|
||||
import {
|
||||
@@ -11,6 +10,7 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { ChevronDown } from '@/lib/icons'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export interface RailSelectOption {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useStore } from '@nanostores/react'
|
||||
import { ChevronDown, Layers3, Pin, Plus, RefreshCw, Sparkles } from 'lucide-react'
|
||||
import { useMemo } from 'react'
|
||||
import type * as React from 'react'
|
||||
|
||||
@@ -16,6 +15,7 @@ import {
|
||||
} from '@/components/ui/sidebar'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import type { SessionInfo } from '@/hermes'
|
||||
import { Brain, ChevronDown, Layers3, Pin, Plus, RefreshCw } from '@/lib/icons'
|
||||
import { cn } from '@/lib/utils'
|
||||
import {
|
||||
$pinnedSessionIds,
|
||||
@@ -41,7 +41,7 @@ const SIDEBAR_NAV: SidebarNavItem[] = [
|
||||
icon: Plus,
|
||||
action: 'new-session'
|
||||
},
|
||||
{ id: 'skills', label: 'Skills', icon: Sparkles, route: SKILLS_ROUTE },
|
||||
{ id: 'skills', label: 'Skills', icon: Brain, route: SKILLS_ROUTE },
|
||||
{ id: 'artifacts', label: 'Artifacts', icon: Layers3, route: ARTIFACTS_ROUTE }
|
||||
]
|
||||
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { Archive, Copy, Pencil, Pin, Trash2 } from 'lucide-react'
|
||||
import {
|
||||
IconArchive,
|
||||
IconBookmark,
|
||||
IconBookmarkFilled,
|
||||
IconCircleX,
|
||||
IconCopy,
|
||||
IconFileDownload,
|
||||
IconPencil
|
||||
} from '@tabler/icons-react'
|
||||
import type * as React from 'react'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
@@ -10,6 +18,7 @@ import {
|
||||
DropdownMenuTrigger
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { triggerHaptic } from '@/lib/haptics'
|
||||
import { exportSession } from '@/lib/session-export'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { notify, notifyError } from '@/store/notifications'
|
||||
|
||||
@@ -60,19 +69,30 @@ export function SessionActionsMenu({
|
||||
onPin?.()
|
||||
}}
|
||||
>
|
||||
<Pin />
|
||||
{pinned ? <IconBookmarkFilled /> : <IconBookmark />}
|
||||
<span>{pinned ? 'Unpin' : 'Pin'}</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className={itemClass} onSelect={() => void copyId()}>
|
||||
<Copy />
|
||||
<IconCopy />
|
||||
<span>Copy ID</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className={itemClass}
|
||||
disabled={!sessionId}
|
||||
onSelect={() => {
|
||||
triggerHaptic('selection')
|
||||
void exportSession(sessionId, { title })
|
||||
}}
|
||||
>
|
||||
<IconFileDownload />
|
||||
<span>Export</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className={itemClass}>
|
||||
<Pencil />
|
||||
<IconPencil />
|
||||
<span>Rename</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className={itemClass}>
|
||||
<Archive />
|
||||
<IconArchive />
|
||||
<span>Add to project</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator className="my-3" />
|
||||
@@ -85,7 +105,7 @@ export function SessionActionsMenu({
|
||||
}}
|
||||
variant="destructive"
|
||||
>
|
||||
<Trash2 />
|
||||
<IconCircleX />
|
||||
<span>Delete</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { MoreVertical } from 'lucide-react'
|
||||
import type * as React from 'react'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import type { SessionInfo } from '@/hermes'
|
||||
import { sessionTitle } from '@/lib/chat-runtime'
|
||||
import { triggerHaptic } from '@/lib/haptics'
|
||||
import { MoreVertical } from '@/lib/icons'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
import { SessionActionsMenu } from './session-actions-menu'
|
||||
|
||||
Reference in New Issue
Block a user