mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-29 18:46:59 +00:00
feat(desktop): strip the header chrome off markdown code blocks
A fenced block carried a bordered card with a "Code · <lang>" title row and a pinned copy button, which read as an attached artifact next to the reply. Drop the border and the header: the block is now a tinted slab on --ui-bg-editor with syntax highlighting and a copy button that reveals on hover. The streaming glow moves entirely to box-shadow since there is no border left to animate.
This commit is contained in:
parent
cfd79e5b1b
commit
b7fc36cf3c
3 changed files with 20 additions and 69 deletions
|
|
@ -4,15 +4,16 @@ import { Codicon, type CodiconProps } from '@/components/ui/codicon'
|
|||
import { cn } from '@/lib/utils'
|
||||
|
||||
/**
|
||||
* Rounded-card shell for fenced code (and any equivalent: diffs, raw payloads,
|
||||
* etc.) sized for the conversation column. Mirrors the expanded tool-row
|
||||
* pattern so code blocks read as the same family of artifact.
|
||||
* Rounded surface for fenced code (and any equivalent: diffs, raw payloads,
|
||||
* etc.) sized for the conversation column. Background only — no border, no
|
||||
* header, no language label — so a code block reads as a tinted slab of the
|
||||
* reply rather than an attached artifact.
|
||||
*/
|
||||
function CodeCard({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'min-w-0 max-w-full overflow-hidden rounded-[0.625rem] border border-border text-[length:var(--conversation-tool-font-size)] text-muted-foreground',
|
||||
'group/code relative min-w-0 max-w-full overflow-hidden rounded-[0.625rem] bg-(--ui-bg-editor) [--expandable-fade-from:var(--ui-bg-editor)] text-[length:var(--conversation-tool-font-size)] text-muted-foreground',
|
||||
className
|
||||
)}
|
||||
data-slot="code-card"
|
||||
|
|
@ -21,31 +22,6 @@ function CodeCard({ className, ...props }: React.ComponentProps<'div'>) {
|
|||
)
|
||||
}
|
||||
|
||||
function CodeCardHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
className={cn('flex items-center justify-between gap-2 border-b border-border px-2 py-1.5', className)}
|
||||
data-slot="code-card-header"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CodeCardTitle({ className, children, ...props }: React.ComponentProps<'span'>) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
'flex min-w-0 items-center gap-1.5 truncate text-[length:var(--conversation-tool-font-size)] font-medium leading-(--conversation-line-height) text-foreground/80',
|
||||
className
|
||||
)}
|
||||
data-slot="code-card-title"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function CodeCardIcon({ className, ...props }: CodiconProps) {
|
||||
return (
|
||||
<Codicon
|
||||
|
|
@ -56,12 +32,6 @@ function CodeCardIcon({ className, ...props }: CodiconProps) {
|
|||
)
|
||||
}
|
||||
|
||||
function CodeCardSubtitle({ className, ...props }: React.ComponentProps<'span'>) {
|
||||
return (
|
||||
<span className={cn('font-normal text-muted-foreground', className)} data-slot="code-card-subtitle" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
function CodeCardBody({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
|
|
@ -75,4 +45,4 @@ function CodeCardBody({ className, ...props }: React.ComponentProps<'div'>) {
|
|||
)
|
||||
}
|
||||
|
||||
export { CodeCard, CodeCardBody, CodeCardHeader, CodeCardIcon, CodeCardSubtitle, CodeCardTitle }
|
||||
export { CodeCard, CodeCardBody, CodeCardIcon }
|
||||
|
|
|
|||
|
|
@ -4,24 +4,18 @@ import type { SyntaxHighlighterProps } from '@assistant-ui/react-streamdown'
|
|||
import { type ComponentProps, type FC, lazy, Suspense, useMemo } from 'react'
|
||||
import type ShikiHighlighter from 'react-shiki'
|
||||
|
||||
import {
|
||||
CodeCard,
|
||||
CodeCardBody,
|
||||
CodeCardHeader,
|
||||
CodeCardIcon,
|
||||
CodeCardSubtitle,
|
||||
CodeCardTitle
|
||||
} from '@/components/chat/code-card'
|
||||
import { CodeCard, CodeCardBody } from '@/components/chat/code-card'
|
||||
import { ExpandableBlock } from '@/components/chat/expandable-block'
|
||||
import { CopyButton } from '@/components/ui/copy-button'
|
||||
import { useI18n } from '@/i18n'
|
||||
import { codiconForLanguage, isLikelyProseCodeBlock, sanitizeLanguageTag } from '@/lib/markdown-code'
|
||||
import { isLikelyProseCodeBlock } from '@/lib/markdown-code'
|
||||
|
||||
/**
|
||||
* Streamdown's code adapter renders header + body as inline siblings, so we
|
||||
* own the wrapping `<CodeCard>` here and neutralize the upstream
|
||||
* `data-streamdown="code-block"` chrome from styles.css. Anything that wants
|
||||
* a card-shaped code surface should compose `CodeCard*` directly.
|
||||
* `data-streamdown="code-block"` chrome from styles.css. The card is
|
||||
* background-only — no header row, no language label — so a fence reads as a
|
||||
* tinted slab of the reply; copy is a hover-reveal control in the corner.
|
||||
*
|
||||
* `react-shiki` full bundle so all `bundledLanguages` work; theme switches
|
||||
* follow the document `color-scheme` via `defaultColor="light-dark()"`.
|
||||
|
|
@ -148,28 +142,19 @@ export const SyntaxHighlighter: FC<HermesSyntaxHighlighterProps> = ({
|
|||
return <div className="aui-prose-fence whitespace-pre-wrap wrap-anywhere text-foreground">{trimmed}</div>
|
||||
}
|
||||
|
||||
const cleanLanguage = sanitizeLanguageTag(language || '')
|
||||
const label = cleanLanguage && cleanLanguage !== 'unknown' ? cleanLanguage : ''
|
||||
const plain = defer || exceedsHighlightBudget(trimmed)
|
||||
|
||||
return (
|
||||
<CodeCard data-streaming={defer ? 'true' : undefined}>
|
||||
<CodeCardHeader>
|
||||
<CodeCardTitle>
|
||||
<CodeCardIcon name={codiconForLanguage(label)} />
|
||||
{t.assistant.tool.code}
|
||||
{label && <CodeCardSubtitle> · {label}</CodeCardSubtitle>}
|
||||
</CodeCardTitle>
|
||||
<CopyButton
|
||||
appearance="inline"
|
||||
className="-my-1 -mr-1 h-5 px-1 opacity-55 hover:opacity-100"
|
||||
iconClassName="size-2.5"
|
||||
label={t.assistant.tool.copyCode}
|
||||
showLabel={false}
|
||||
text={trimmed}
|
||||
/>
|
||||
</CodeCardHeader>
|
||||
<CodeCardBody>
|
||||
<CopyButton
|
||||
appearance="inline"
|
||||
className="absolute right-1.5 top-1.5 z-10 h-5 gap-0 rounded-md px-1 opacity-0 transition-opacity group-hover/code:opacity-100 focus-visible:opacity-100"
|
||||
iconClassName="size-2.5"
|
||||
label={t.assistant.tool.copyCode}
|
||||
showLabel={false}
|
||||
text={trimmed}
|
||||
/>
|
||||
<CodeCardBody className="[&_pre]:px-3 [&_pre]:py-2.5">
|
||||
<ExpandableBlock>
|
||||
<Pre className="aui-shiki m-0 overflow-hidden bg-transparent p-0">
|
||||
{plain ? (
|
||||
|
|
|
|||
|
|
@ -1356,7 +1356,6 @@ text-* variant utilities. */ .btn-arc {
|
|||
margin-block: var(--paragraph-gap) 0;
|
||||
position: relative;
|
||||
transition:
|
||||
border-color 180ms ease-out,
|
||||
box-shadow 180ms ease-out,
|
||||
background-color 180ms ease-out;
|
||||
}
|
||||
|
|
@ -1365,7 +1364,6 @@ text-* variant utilities. */ .btn-arc {
|
|||
animation:
|
||||
code-card-stream-enter 180ms cubic-bezier(0.16, 1, 0.3, 1) both,
|
||||
code-card-stream-glow 1.8s ease-in-out 180ms infinite alternate;
|
||||
border-color: color-mix(in srgb, var(--dt-ring) 24%, var(--ui-stroke-tertiary));
|
||||
box-shadow:
|
||||
0 0 0 0.0625rem color-mix(in srgb, var(--dt-ring) 10%, transparent),
|
||||
0 0.625rem 1.75rem color-mix(in srgb, var(--dt-ring) 8%, transparent);
|
||||
|
|
@ -1678,14 +1676,12 @@ text-* variant utilities. */ .btn-arc {
|
|||
|
||||
@keyframes code-card-stream-glow {
|
||||
from {
|
||||
border-color: color-mix(in srgb, var(--dt-ring) 18%, var(--ui-stroke-tertiary));
|
||||
box-shadow:
|
||||
0 0 0 0.0625rem color-mix(in srgb, var(--dt-ring) 6%, transparent),
|
||||
0 0.5rem 1.5rem color-mix(in srgb, var(--dt-ring) 5%, transparent);
|
||||
}
|
||||
|
||||
to {
|
||||
border-color: color-mix(in srgb, var(--dt-ring) 32%, var(--ui-stroke-tertiary));
|
||||
box-shadow:
|
||||
0 0 0 0.0625rem color-mix(in srgb, var(--dt-ring) 12%, transparent),
|
||||
0 0.75rem 2rem color-mix(in srgb, var(--dt-ring) 10%, transparent);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue