From 3f701fcee36bf6a2d7f1fd017ba3ecc8ea23e8e8 Mon Sep 17 00:00:00 2001 From: Enrico Ros Date: Wed, 12 Mar 2025 00:09:17 -0700 Subject: [PATCH] Void Annotations: render --- .../BlockPartModelAnnotations.tsx | 185 ++++++++++++++++++ .../fragments-content/ContentFragments.tsx | 10 +- 2 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 src/apps/chat/components/message/fragments-content/BlockPartModelAnnotations.tsx diff --git a/src/apps/chat/components/message/fragments-content/BlockPartModelAnnotations.tsx b/src/apps/chat/components/message/fragments-content/BlockPartModelAnnotations.tsx new file mode 100644 index 000000000..2f8f14836 --- /dev/null +++ b/src/apps/chat/components/message/fragments-content/BlockPartModelAnnotations.tsx @@ -0,0 +1,185 @@ +import * as React from 'react'; + +import { Box, Button, List, ListItem, ListItemButton } from '@mui/joy'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; + +import { useScaledTypographySx } from '~/modules/blocks/blocks.styles'; + +import type { ContentScaling } from '~/common/app.theme'; +import type { DVoidWebCitation } from '~/common/stores/chat/chat.fragments'; +import { AvatarDomainFavicon } from '~/common/components/AvatarDomainFavicon'; +import { ExpanderControlledBox } from '~/common/components/ExpanderControlledBox'; +import { TooltipOutlined } from '~/common/components/TooltipOutlined'; +import { urlExtractDomain, urlPrettyHref } from '~/common/util/urlUtils'; + + +// configuration +const MAX_ICONS = 10; +const COLOR = 'neutral'; + + +const styles = { + + iconRowButton: { + minHeight: '2.5rem', + gap: 0.5, + px: 1.5, + border: 'none', + transition: 'transform 0.14s ease', + '&:hover': { transform: 'translateY(-1px)' } as const, + } as const, + + citationsList: { + boxShadow: `inset 1px 1px 3px -3px var(--joy-palette-${COLOR}-solidBg)`, + borderRadius: 'sm', + border: '1px solid', + borderColor: `${COLOR}.outlinedBorder`, + backgroundColor: `rgb(var(--joy-palette-${COLOR}-lightChannel) / 10%)`, + mt: 1, + overflow: 'hidden', + } as const, + + citationItem: { + // py: 0.75, + gap: 1.5, + borderRadius: 0, + borderBottom: '1px solid', + borderBottomColor: 'divider', + } as const, + + citationItemLast: { + // py: 0.75, + gap: 1.5, + border: 'none', + } as const, + + citationNumber: { + minWidth: 26, + textAlign: 'end', + // color: 'text.tertiary', + // fontWeight: 'lg', + } as const, + + line: { + flex: 1, + display: 'flex', + alignItems: 'center', + gap: 1, + } as const, + + lineContent: { + display: 'flex', + flexDirection: 'column', + overflow: 'hidden', + } as const, + + lineLink: { + fontStyle: 'italic', + fontSize: 'xs', + opacity: 0.5, + } as const, + +} as const; + + +export function BlockPartModelAnnotations(props: { + itemsName?: string; + annotations: DVoidWebCitation[]; + contentScaling: ContentScaling; +}) { + + // state + const [expanded, setExpanded] = React.useState(false); + + // external state + const scaledTypographySx = useScaledTypographySx(props.contentScaling, false, false); + + // derived + const annotationsCount = props.annotations.length; + const moreIcons = annotationsCount - MAX_ICONS; + + const handleToggleExpanded = React.useCallback(() => setExpanded(on => !on), []); + + if (!annotationsCount) + return null; + + return ( + + + {/* Row of favicons */} + + + {/* Expanded citations list */} + + + + {props.annotations.map((citation, index) => { + const domain = urlExtractDomain(citation.url); + + return ( + + + + {citation.refNumber ? `[${citation.refNumber}]` : index + 1} + + + + {expanded && } + + + {citation.title || domain} + + + {urlPrettyHref(citation.url)} + + + + + + + + ); + })} + + + + + ); +} diff --git a/src/apps/chat/components/message/fragments-content/ContentFragments.tsx b/src/apps/chat/components/message/fragments-content/ContentFragments.tsx index 0638e3564..54d105edc 100644 --- a/src/apps/chat/components/message/fragments-content/ContentFragments.tsx +++ b/src/apps/chat/components/message/fragments-content/ContentFragments.tsx @@ -13,6 +13,7 @@ import { BlockEdit_TextFragment } from './BlockEdit_TextFragment'; import { BlockOpEmpty } from './BlockOpEmpty'; import { BlockPartError } from './BlockPartError'; import { BlockPartImageRef } from './BlockPartImageRef'; +import { BlockPartModelAnnotations } from './BlockPartModelAnnotations'; import { BlockPartModelAux } from './BlockPartModelAux'; import { BlockPartPlaceholder } from './BlockPartPlaceholder'; import { BlockPartText_AutoBlocks } from './BlockPartText_AutoBlocks'; @@ -113,8 +114,13 @@ export function ContentFragments(props: { const { fId, part } = fragment; switch (part.pt) { case 'annotations': - // TODO: render - return null; + return ( + + ); case 'ma': return (