mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
feat: improve event presentation with separate icons and add aria labels to buttons
This commit is contained in:
@@ -7,31 +7,39 @@ export function formatUnixTimestamp(value: number): string {
|
||||
return date.toLocaleString()
|
||||
}
|
||||
|
||||
export function renderEventLabel(event: AgentEvent): string {
|
||||
export type EventPresentation = {
|
||||
icon: string | null
|
||||
text: string
|
||||
}
|
||||
|
||||
export function getEventPresentation(event: AgentEvent): EventPresentation {
|
||||
if (event.type === 'switch') {
|
||||
const mode = event.mode === 'local' ? 'local' : 'remote'
|
||||
return `🔄 Switched to ${mode}`
|
||||
return { icon: '🔄', text: `Switched to ${mode}` }
|
||||
}
|
||||
if (event.type === 'title-changed') {
|
||||
const title = typeof event.title === 'string' ? event.title : ''
|
||||
return title ? `Title changed to "${title}"` : 'Title changed'
|
||||
return { icon: null, text: title ? `Title changed to "${title}"` : 'Title changed' }
|
||||
}
|
||||
if (event.type === 'permission-mode-changed') {
|
||||
const modeValue = (event as Record<string, unknown>).mode
|
||||
const mode = typeof modeValue === 'string' ? modeValue : 'default'
|
||||
return `🔐 Permission mode: ${mode}`
|
||||
return { icon: '🔐', text: `Permission mode: ${mode}` }
|
||||
}
|
||||
if (event.type === 'limit-reached') {
|
||||
const endsAt = typeof event.endsAt === 'number' ? event.endsAt : null
|
||||
return endsAt ? `⏳ Usage limit reached until ${formatUnixTimestamp(endsAt)}` : '⏳ Usage limit reached'
|
||||
return { icon: '⏳', text: endsAt ? `Usage limit reached until ${formatUnixTimestamp(endsAt)}` : 'Usage limit reached' }
|
||||
}
|
||||
if (event.type === 'message') {
|
||||
return typeof event.message === 'string' ? event.message : 'Message'
|
||||
return { icon: null, text: typeof event.message === 'string' ? event.message : 'Message' }
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(event)
|
||||
return { icon: null, text: JSON.stringify(event) }
|
||||
} catch {
|
||||
return String(event.type)
|
||||
return { icon: null, text: String(event.type) }
|
||||
}
|
||||
}
|
||||
|
||||
export function renderEventLabel(event: AgentEvent): string {
|
||||
return getEventPresentation(event).text
|
||||
}
|
||||
|
||||
@@ -86,6 +86,8 @@ export function ComposerButtons(props: {
|
||||
{props.showSettingsButton ? (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Settings"
|
||||
title="Settings"
|
||||
className="settings-button flex h-8 w-8 items-center justify-center rounded-full text-[var(--app-fg)]/60 transition-colors hover:bg-[var(--app-bg)] hover:text-[var(--app-fg)]"
|
||||
onClick={props.onSettingsToggle}
|
||||
disabled={props.controlsDisabled}
|
||||
@@ -97,6 +99,8 @@ export function ComposerButtons(props: {
|
||||
{props.showAbortButton ? (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Abort"
|
||||
title="Abort"
|
||||
disabled={props.abortDisabled}
|
||||
className="flex h-8 w-8 items-center justify-center rounded-full text-[var(--app-fg)]/60 transition-colors hover:bg-[var(--app-bg)] hover:text-red-500 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
onClick={props.onAbort}
|
||||
@@ -108,6 +112,8 @@ export function ComposerButtons(props: {
|
||||
|
||||
<ComposerPrimitive.Send
|
||||
disabled={props.controlsDisabled || !props.canSend}
|
||||
aria-label="Send"
|
||||
title="Send"
|
||||
className={`flex h-8 w-8 items-center justify-center rounded-full transition-colors ${
|
||||
props.canSend && !props.controlsDisabled
|
||||
? 'bg-black text-white'
|
||||
@@ -119,4 +125,3 @@ export function ComposerButtons(props: {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
import { MessagePrimitive, useAssistantState } from '@assistant-ui/react'
|
||||
import { getEventPresentation } from '@/chat/presentation'
|
||||
import type { HappyChatMessageMetadata } from '@/lib/assistant-runtime'
|
||||
|
||||
export function HappySystemMessage() {
|
||||
const message = useAssistantState(({ message }) => message)
|
||||
if (message.role !== 'system') return null
|
||||
|
||||
const text = message.content[0]?.type === 'text' ? message.content[0].text : ''
|
||||
const custom = message.metadata.custom as Partial<HappyChatMessageMetadata> | undefined
|
||||
const event = custom?.kind === 'event' ? custom.event : undefined
|
||||
const icon = event ? getEventPresentation(event).icon : null
|
||||
|
||||
return (
|
||||
<MessagePrimitive.Root className="py-1">
|
||||
<div className="mx-auto w-fit max-w-[92%] px-2 text-center text-xs text-[var(--app-hint)] opacity-80">
|
||||
{text}
|
||||
<span className="inline-flex items-center gap-1">
|
||||
{icon ? <span aria-hidden="true">{icon}</span> : null}
|
||||
<span>{text}</span>
|
||||
</span>
|
||||
</div>
|
||||
</MessagePrimitive.Root>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ToolCallMessagePartProps } from '@assistant-ui/react'
|
||||
import type { ChatBlock } from '@/chat/types'
|
||||
import type { ToolCallBlock } from '@/chat/types'
|
||||
import { renderEventLabel } from '@/chat/presentation'
|
||||
import { getEventPresentation } from '@/chat/presentation'
|
||||
import { CodeBlock } from '@/components/CodeBlock'
|
||||
import { MarkdownRenderer } from '@/components/MarkdownRenderer'
|
||||
import { LazyRainbowText } from '@/components/LazyRainbowText'
|
||||
@@ -77,10 +77,14 @@ function HappyNestedBlockList(props: {
|
||||
}
|
||||
|
||||
if (block.kind === 'agent-event') {
|
||||
const presentation = getEventPresentation(block.event)
|
||||
return (
|
||||
<div key={`event:${block.id}`} className="py-1">
|
||||
<div className="mx-auto w-fit max-w-[92%] px-2 text-center text-xs text-[var(--app-hint)] opacity-80">
|
||||
{renderEventLabel(block.event)}
|
||||
<span className="inline-flex items-center gap-1">
|
||||
{presentation.icon ? <span aria-hidden="true">{presentation.icon}</span> : null}
|
||||
<span>{presentation.text}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user