mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-10 20:29:00 +08:00
Compare commits
1 Commits
v2026.6.5
...
fix/slash-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13196da0d3 |
@@ -24,7 +24,12 @@ export const COMPLETION_DRAWER_BELOW_CLASS = [
|
||||
|
||||
export const COMPLETION_DRAWER_ROW_CLASS = [
|
||||
'relative flex cursor-default select-none items-center gap-2 rounded-md px-2 py-1',
|
||||
'w-full min-w-0 text-left text-xs outline-hidden transition-colors',
|
||||
'w-full min-w-0 text-left text-xs outline-hidden',
|
||||
// Keyboard selection (data-highlighted / activeIndex) should feel instant —
|
||||
// no transition on background so the highlight snaps to the new row.
|
||||
// Mouse hover keeps a smooth 200ms color transition for that buttery feel.
|
||||
'transition-[color,border-color,text-decoration-color] duration-0',
|
||||
'hover:transition-colors hover:duration-200',
|
||||
'hover:bg-(--ui-bg-tertiary)',
|
||||
'data-[highlighted]:bg-(--ui-bg-tertiary) data-[highlighted]:text-foreground'
|
||||
].join(' ')
|
||||
|
||||
@@ -65,7 +65,7 @@ import {
|
||||
} from './rich-editor'
|
||||
import { SkinSlashPopover } from './skin-slash-popover'
|
||||
import { detectTrigger, extractClipboardImageBlobs, textBeforeCaret, type TriggerState } from './text-utils'
|
||||
import { ComposerTriggerPopover } from './trigger-popover'
|
||||
import { ComposerTriggerPopover, type ComposerTriggerPopoverHandle } from './trigger-popover'
|
||||
import type { ChatBarProps } from './types'
|
||||
import { UrlDialog } from './url-dialog'
|
||||
import { VoiceActivity, VoicePlaybackActivity } from './voice-activity'
|
||||
@@ -125,6 +125,7 @@ export function ChatBar({
|
||||
const previousBusyRef = useRef(busy)
|
||||
const drainingQueueRef = useRef(false)
|
||||
const urlInputRef = useRef<HTMLInputElement | null>(null)
|
||||
const triggerPopoverRef = useRef<ComposerTriggerPopoverHandle>(null)
|
||||
|
||||
const [urlOpen, setUrlOpen] = useState(false)
|
||||
const [urlValue, setUrlValue] = useState('')
|
||||
@@ -441,8 +442,19 @@ export function ChatBar({
|
||||
const before = textBeforeCaret(editor)
|
||||
const detected = detectTrigger(before ?? composerPlainText(editor))
|
||||
|
||||
// Only reset the active index when the trigger state actually changed
|
||||
// (new trigger opened, trigger closed, or query text changed). When the
|
||||
// kind + query are the same (e.g. the user just pressed ArrowUp/Down),
|
||||
// preserve the selection so keyboard navigation isn't wiped out by the
|
||||
// keyup→refreshTrigger round-trip.
|
||||
const queryChanged =
|
||||
trigger?.kind !== detected?.kind || trigger?.query !== detected?.query
|
||||
|
||||
setTrigger(detected)
|
||||
setTriggerActive(0)
|
||||
|
||||
if (queryChanged) {
|
||||
setTriggerActive(0)
|
||||
}
|
||||
}, [trigger])
|
||||
|
||||
const handleEditorInput = (event: FormEvent<HTMLDivElement>) => {
|
||||
@@ -559,6 +571,7 @@ export function ChatBar({
|
||||
if (event.key === 'ArrowDown') {
|
||||
event.preventDefault()
|
||||
setTriggerActive(idx => (idx + 1) % triggerItems.length)
|
||||
requestAnimationFrame(() => triggerPopoverRef.current?.scrollActiveIntoView())
|
||||
|
||||
return
|
||||
}
|
||||
@@ -566,6 +579,7 @@ export function ChatBar({
|
||||
if (event.key === 'ArrowUp') {
|
||||
event.preventDefault()
|
||||
setTriggerActive(idx => (idx - 1 + triggerItems.length) % triggerItems.length)
|
||||
requestAnimationFrame(() => triggerPopoverRef.current?.scrollActiveIntoView())
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1092,6 +1106,7 @@ export function ChatBar({
|
||||
{showHelpHint && <HelpHint />}
|
||||
{trigger && (
|
||||
<ComposerTriggerPopover
|
||||
ref={triggerPopoverRef}
|
||||
activeIndex={triggerActive}
|
||||
items={triggerItems}
|
||||
kind={trigger.kind}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Unstable_TriggerItem } from '@assistant-ui/core'
|
||||
import { forwardRef, useImperativeHandle, useRef } from 'react'
|
||||
|
||||
import { Codicon } from '@/components/ui/codicon'
|
||||
import { cn } from '@/lib/utils'
|
||||
@@ -51,21 +52,47 @@ interface ComposerTriggerPopoverProps {
|
||||
placement?: 'bottom' | 'top'
|
||||
}
|
||||
|
||||
export function ComposerTriggerPopover({
|
||||
activeIndex,
|
||||
items,
|
||||
kind,
|
||||
loading,
|
||||
onHover,
|
||||
onPick,
|
||||
placement = 'top'
|
||||
}: ComposerTriggerPopoverProps) {
|
||||
export interface ComposerTriggerPopoverHandle {
|
||||
scrollActiveIntoView: () => void
|
||||
}
|
||||
|
||||
export const ComposerTriggerPopover = forwardRef<
|
||||
ComposerTriggerPopoverHandle,
|
||||
ComposerTriggerPopoverProps
|
||||
>(function ComposerTriggerPopover(
|
||||
{ activeIndex, items, kind, loading, onHover, onPick, placement = 'top' },
|
||||
ref
|
||||
) {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
// Expose scrollActiveIntoView so the keyboard handler in the parent can
|
||||
// trigger a scroll only on arrow-key events — mouse hover never calls this.
|
||||
useImperativeHandle(ref, () => ({
|
||||
scrollActiveIntoView() {
|
||||
const container = containerRef.current
|
||||
if (!container) return
|
||||
|
||||
const highlighted = container.querySelector<HTMLElement>('[data-highlighted]')
|
||||
if (!highlighted) return
|
||||
|
||||
const buttonRect = highlighted.getBoundingClientRect()
|
||||
const containerRect = container.getBoundingClientRect()
|
||||
|
||||
if (buttonRect.top < containerRect.top) {
|
||||
container.scrollTop -= containerRect.top - buttonRect.top
|
||||
} else if (buttonRect.bottom > containerRect.bottom) {
|
||||
container.scrollTop += buttonRect.bottom - containerRect.bottom
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
return (
|
||||
<div
|
||||
className={placement === 'bottom' ? COMPLETION_DRAWER_BELOW_CLASS : COMPLETION_DRAWER_CLASS}
|
||||
data-slot="composer-completion-drawer"
|
||||
data-state="open"
|
||||
onMouseDown={event => event.preventDefault()}
|
||||
ref={containerRef}
|
||||
role="listbox"
|
||||
>
|
||||
{items.length === 0 ? (
|
||||
@@ -86,11 +113,12 @@ export function ComposerTriggerPopover({
|
||||
const meta = item.metadata as { display?: string; meta?: string } | undefined
|
||||
const display = meta?.display ?? (kind === '/' ? `/${item.label}` : item.label)
|
||||
const description = meta?.meta || item.description
|
||||
const isActive = index === activeIndex
|
||||
|
||||
return (
|
||||
<button
|
||||
className={cn(COMPLETION_DRAWER_ROW_CLASS, index === activeIndex && 'bg-(--ui-bg-tertiary)')}
|
||||
data-highlighted={index === activeIndex ? '' : undefined}
|
||||
className={cn(COMPLETION_DRAWER_ROW_CLASS, isActive && 'bg-(--ui-bg-tertiary)')}
|
||||
data-highlighted={isActive ? '' : undefined}
|
||||
key={item.id}
|
||||
onClick={() => onPick(item)}
|
||||
onMouseEnter={() => onHover(index)}
|
||||
@@ -109,4 +137,4 @@ export function ComposerTriggerPopover({
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user