refactor atom components to use .withProps() subcomponents and extract inline code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
cliffhall 2026-03-24 10:44:06 -04:00
commit 04c23094a4
7 changed files with 168 additions and 82 deletions

View file

@ -16,11 +16,31 @@ function formatJson(content: string): string {
} }
} }
function buildDataUri(mimeType: string, content: string): string {
return `data:${mimeType};base64,${content}`;
}
function formatContent(type: "text" | "json", content: string): string {
return type === "json" ? formatJson(content) : content;
}
const ContentWrapper = Flex.withProps({ const ContentWrapper = Flex.withProps({
pos: "relative", pos: "relative",
direction: "column", direction: "column",
}); });
const CopyOverlay = Flex.withProps({
pos: "absolute",
top: 4,
right: 4,
});
const PreviewImage = Image.withProps({
alt: "Content preview",
maw: 400,
radius: "md",
});
export function ContentViewer({ export function ContentViewer({
type, type,
content, content,
@ -34,26 +54,21 @@ export function ContentViewer({
{(type === "json" || type === "text") && ( {(type === "json" || type === "text") && (
<ContentWrapper> <ContentWrapper>
<Code block p={36}> <Code block p={36}>
{type === "json" ? formatJson(content) : content} {formatContent(type, content)}
</Code> </Code>
{showCopy && ( {showCopy && (
<Flex pos="absolute" top={4} right={4}> <CopyOverlay>
<CopyButton value={content} /> <CopyButton value={content} />
</Flex> </CopyOverlay>
)} )}
</ContentWrapper> </ContentWrapper>
)} )}
{type === "image" && ( {type === "image" && (
<Image <PreviewImage src={buildDataUri(mimeType || "image/png", content)} />
src={`data:${mimeType || "image/png"};base64,${content}`}
alt="Content preview"
maw={400}
radius="md"
/>
)} )}
{type === "audio" && ( {type === "audio" && (
<audio controls> <audio controls>
<source src={`data:${mimeType || "audio/wav"};base64,${content}`} /> <source src={buildDataUri(mimeType || "audio/wav", content)} />
</audio> </audio>
)} )}
</Stack> </Stack>

View file

@ -9,6 +9,46 @@ export interface InlineErrorProps {
docLink?: string; docLink?: string;
} }
const HeaderRow = Group.withProps({
justify: "space-between",
wrap: "nowrap",
});
const MessageGroup = Group.withProps({
gap: "sm",
wrap: "nowrap",
});
const ErrorMessage = Text.withProps({
size: "sm",
fw: 500,
c: "red",
});
const RetryText = Text.withProps({
size: "sm",
c: "dimmed",
});
const ExpandButton = Button.withProps({
variant: "transparent",
size: "compact-xs",
c: "var(--inspector-text-primary)",
});
const DocLinkButton = Button.withProps({
variant: "light",
size: "xs",
component: "a",
target: "_blank",
});
function formatRetryLabel(retryCount: number, maxRetries?: number): string {
return maxRetries !== undefined
? `Retry attempt ${retryCount} of ${maxRetries}`
: `Retry attempt ${retryCount}`;
}
export function InlineError({ export function InlineError({
message, message,
details, details,
@ -22,42 +62,26 @@ export function InlineError({
return ( return (
<Alert color="red" variant="light"> <Alert color="red" variant="light">
<Stack gap="xs"> <Stack gap="xs">
<Group justify="space-between" wrap="nowrap"> <HeaderRow>
<Group gap="sm" wrap="nowrap"> <MessageGroup>
<Text size="sm" fw={500} c="red"> <ErrorMessage>{message}</ErrorMessage>
{message}
</Text>
{retryCount !== undefined && ( {retryCount !== undefined && (
<Text size="sm" c="dimmed"> <RetryText>{formatRetryLabel(retryCount, maxRetries)}</RetryText>
Retry attempt {retryCount}
{maxRetries !== undefined && ` of ${maxRetries}`}
</Text>
)} )}
</Group> </MessageGroup>
{hasExpandable && ( {hasExpandable && (
<Button <ExpandButton onClick={() => setExpanded((v) => !v)}>
variant="transparent"
size="compact-xs"
c="var(--inspector-text-primary)"
onClick={() => setExpanded((v) => !v)}
>
{expanded ? "Show less" : "Show more"} {expanded ? "Show less" : "Show more"}
</Button> </ExpandButton>
)} )}
</Group> </HeaderRow>
<Collapse in={expanded}> <Collapse in={expanded}>
<Stack gap="xs"> <Stack gap="xs">
{details && <Text size="sm">{details}</Text>} {details && <Text size="sm">{details}</Text>}
{docLink && ( {docLink && (
<Button <DocLinkButton href={docLink}>
variant="light"
size="xs"
component="a"
href={docLink}
target="_blank"
>
View Troubleshooting Guide View Troubleshooting Guide
</Button> </DocLinkButton>
)} )}
</Stack> </Stack>
</Collapse> </Collapse>

View file

@ -1,17 +1,27 @@
import { Button, Flex, Group, Text } from "@mantine/core"; import { Button, Group, Paper, Text } from "@mantine/core";
export interface ListChangedIndicatorProps { export interface ListChangedIndicatorProps {
visible: boolean; visible: boolean;
onRefresh: () => void; onRefresh: () => void;
} }
const Dot = Flex.withProps({ const Dot = Paper.withProps({
w: 8, w: 8,
h: 8, h: 8,
style: { borderRadius: "50%" }, radius: "xl",
bg: "var(--inspector-status-connecting)", bg: "var(--inspector-status-connecting)",
}); });
const UpdateLabel = Text.withProps({
size: "sm",
c: "dimmed",
});
const RefreshButton = Button.withProps({
size: "xs",
variant: "light",
});
export function ListChangedIndicator({ export function ListChangedIndicator({
visible, visible,
onRefresh, onRefresh,
@ -21,12 +31,8 @@ export function ListChangedIndicator({
return ( return (
<Group gap="xs"> <Group gap="xs">
<Dot /> <Dot />
<Text size="sm" c="dimmed"> <UpdateLabel>List updated</UpdateLabel>
List updated <RefreshButton onClick={onRefresh}>↻ Refresh</RefreshButton>
</Text>
<Button size="xs" variant="light" onClick={onRefresh}>
↻ Refresh
</Button>
</Group> </Group>
); );
} }

View file

@ -54,24 +54,37 @@ function isBoldLevel(level: LogLevel): boolean {
return level === "alert" || level === "emergency"; return level === "alert" || level === "emergency";
} }
function getBoldWeight(level: LogLevel): number | undefined {
return isBoldLevel(level) ? 700 : undefined;
}
function formatLogger(logger: string): string {
return `[${logger}]`;
}
const TimestampText = Text.withProps({
size: "sm",
ff: "monospace",
c: "dimmed",
});
const LoggerText = Text.withProps({
size: "xs",
c: "dimmed",
});
export function LogEntry({ timestamp, level, message, logger }: LogEntryProps) { export function LogEntry({ timestamp, level, message, logger }: LogEntryProps) {
return ( return (
<Group gap="sm" wrap="nowrap"> <Group gap="sm" wrap="nowrap">
<Text size="sm" ff="monospace" c="dimmed"> <TimestampText>{timestamp}</TimestampText>
{timestamp}
</Text>
<Badge <Badge
color={levelBadgeColor[level]} color={levelBadgeColor[level]}
variant={levelBadgeVariant[level]} variant={levelBadgeVariant[level]}
fw={isBoldLevel(level) ? 700 : undefined} fw={getBoldWeight(level)}
> >
{level} {level}
</Badge> </Badge>
{logger && ( {logger && <LoggerText>{formatLogger(logger)}</LoggerText>}
<Text size="xs" c="dimmed">
[{logger}]
</Text>
)}
<Text size="sm" c={levelMessageColor[level]}> <Text size="sm" c={levelMessageColor[level]}>
{message} {message}
</Text> </Text>

View file

@ -9,6 +9,35 @@ export interface MessageBubbleProps {
audioContent?: { data: string; mimeType: string }; audioContent?: { data: string; mimeType: string };
} }
function buildDataUri(mimeType: string, data: string): string {
return `data:${mimeType};base64,${data}`;
}
function formatRoleLabel(index: number, role: string): string {
return `[${index}] role: ${role}`;
}
function formatQuotedContent(content: string): string {
return `"${content}"`;
}
const BubbleContainer = Paper.withProps({
p: "md",
radius: "md",
withBorder: true,
});
const RoleLabel = Text.withProps({
size: "xs",
c: "dimmed",
});
const PreviewImage = Image.withProps({
maw: 300,
radius: "sm",
mt: "xs",
});
export function MessageBubble({ export function MessageBubble({
index, index,
role, role,
@ -17,32 +46,27 @@ export function MessageBubble({
audioContent, audioContent,
}: MessageBubbleProps) { }: MessageBubbleProps) {
return ( return (
<Paper p="md" radius="md" withBorder> <BubbleContainer>
<Stack gap="xs"> <Stack gap="xs">
<Group justify="space-between"> <Group justify="space-between">
<Text size="xs" c="dimmed"> <RoleLabel>{formatRoleLabel(index, role)}</RoleLabel>
[{index}] role: {role}
</Text>
<CopyButton value={content} /> <CopyButton value={content} />
</Group> </Group>
<Text size="sm">&quot;{content}&quot;</Text> <Text size="sm">{formatQuotedContent(content)}</Text>
{imageContent && ( {imageContent && (
<Image <PreviewImage
src={`data:${imageContent.mimeType};base64,${imageContent.data}`} src={buildDataUri(imageContent.mimeType, imageContent.data)}
maw={300}
radius="sm"
mt="xs"
/> />
)} )}
{audioContent && ( {audioContent && (
<audio controls> <audio controls>
<source <source
src={`data:${audioContent.mimeType};base64,${audioContent.data}`} src={buildDataUri(audioContent.mimeType, audioContent.data)}
type={audioContent.mimeType} type={audioContent.mimeType}
/> />
</audio> </audio>
)} )}
</Stack> </Stack>
</Paper> </BubbleContainer>
); );
} }

View file

@ -6,6 +6,20 @@ export interface ProgressDisplayProps {
elapsed?: string; elapsed?: string;
} }
function formatPercent(progress: number): string {
return `${progress}%`;
}
const ProgressLabel = Text.withProps({
size: "sm",
c: "dimmed",
});
const ElapsedText = Text.withProps({
size: "xs",
c: "dimmed",
});
export function ProgressDisplay({ export function ProgressDisplay({
progress, progress,
description, description,
@ -14,21 +28,11 @@ export function ProgressDisplay({
return ( return (
<Stack gap="xs"> <Stack gap="xs">
<Group justify="space-between"> <Group justify="space-between">
{description && ( {description && <ProgressLabel>{description}</ProgressLabel>}
<Text size="sm" c="dimmed"> <ProgressLabel>{formatPercent(progress)}</ProgressLabel>
{description}
</Text>
)}
<Text size="sm" c="dimmed">
{progress}%
</Text>
</Group> </Group>
<Progress value={progress} size="sm" /> <Progress value={progress} size="sm" />
{elapsed && ( {elapsed && <ElapsedText>{elapsed}</ElapsedText>}
<Text size="xs" c="dimmed">
{elapsed}
</Text>
)}
</Stack> </Stack>
); );
} }

View file

@ -1,4 +1,4 @@
import { Flex, Group, Text } from "@mantine/core"; import { Group, Paper, Text } from "@mantine/core";
export interface StatusIndicatorProps { export interface StatusIndicatorProps {
status: "connected" | "connecting" | "disconnected" | "failed"; status: "connected" | "connecting" | "disconnected" | "failed";
@ -30,10 +30,10 @@ function getLabel(
} }
} }
const Dot = Flex.withProps({ const Dot = Paper.withProps({
w: 10, w: 10,
h: 10, h: 10,
style: { borderRadius: "50%" }, radius: "xl",
}); });
export function StatusIndicator({ export function StatusIndicator({