Align dev workflow with node-based electron-vite dev and tighten assistant markdown layout

This commit is contained in:
Shantur Rathore
2025-10-30 22:58:49 +00:00
parent 37a47699e3
commit 505a06de05
10 changed files with 8173 additions and 60 deletions

View File

@@ -5,6 +5,7 @@ import type { TextPart } from "../types/message"
interface MarkdownProps {
part: TextPart
isDark?: boolean
size?: "base" | "sm" | "tight"
}
export function Markdown(props: MarkdownProps) {
@@ -94,5 +95,17 @@ export function Markdown(props: MarkdownProps) {
})
})
return <div ref={containerRef} class="prose prose-sm dark:prose-invert max-w-none" innerHTML={html()} />
const proseClass = () => {
const classes = ["prose", "dark:prose-invert", "max-w-none"]
if (props.size === "tight") {
classes.push("prose-sm", "prose-tight")
} else if (props.size === "sm") {
classes.push("prose-sm")
}
return classes.join(" ")
}
return <div ref={containerRef} class={proseClass()} innerHTML={html()} />
}

View File

@@ -58,32 +58,33 @@ export default function MessageItem(props: MessageItemProps) {
}
}
const containerClass = () =>
isUser()
? "message-item-base bg-[var(--message-user-bg)] border-l-4 border-[var(--message-user-border)]"
: "message-item-base assistant-message bg-[var(--message-assistant-bg)] border-l-4 border-[var(--message-assistant-border)]"
return (
<div
class={`message-item-base ${
isUser()
? "bg-[var(--message-user-bg)] border-l-4 border-[var(--message-user-border)]"
: "bg-[var(--message-assistant-bg)] border-l-4 border-[var(--message-assistant-border)]"
}`}
>
<div class="flex justify-between items-center gap-3">
<span class="font-semibold text-sm text-[var(--text-muted)]">
<div class={containerClass()}>
<div class="flex justify-between items-center gap-2.5 pb-0.5">
<span class="font-semibold text-xs text-[var(--text-muted)]">
{isUser() ? "You" : "Assistant"}
</span>
<span class="text-xs text-[var(--text-muted)]">{timestamp()}</span>
<Show when={isUser() && props.onRevert}>
<button
class="bg-transparent border border-[var(--border-base)] text-[var(--text-muted)] cursor-pointer px-2 py-0.5 rounded text-base leading-none transition-all duration-200 flex items-center justify-center min-w-7 h-6 hover:bg-[var(--surface-hover)] hover:border-[var(--accent-primary)] hover:text-[var(--accent-primary)] active:scale-95"
onClick={handleRevert}
title="Revert to this message"
aria-label="Revert to this message"
>
</button>
</Show>
<div class="flex items-center gap-2">
<span class="text-[11px] text-[var(--text-muted)]">{timestamp()}</span>
<Show when={isUser() && props.onRevert}>
<button
class="bg-transparent border border-[var(--border-base)] text-[var(--text-muted)] cursor-pointer px-2 py-0.5 rounded text-sm leading-none transition-all duration-200 flex items-center justify-center min-w-7 h-6 hover:bg-[var(--surface-hover)] hover:border-[var(--accent-primary)] hover:text-[var(--accent-primary)] active:scale-95"
onClick={handleRevert}
title="Revert to this message"
aria-label="Revert to this message"
>
</button>
</Show>
</div>
</div>
<div class="pt-1.5 leading-relaxed whitespace-pre-wrap break-words">
<div class="pt-1 whitespace-pre-wrap break-words leading-[1.1]">
<Show when={props.isQueued && isUser()}>
<div class="message-queued-badge">QUEUED</div>
</Show>
@@ -98,7 +99,7 @@ export default function MessageItem(props: MessageItemProps) {
</div>
</Show>
<For each={messageParts()}>{(part) => <MessagePart part={part} />}</For>
<For each={messageParts()}>{(part) => <MessagePart part={part} messageType={props.message.type} />}</For>
</div>
<Show when={props.message.status === "sending"}>

View File

@@ -8,13 +8,15 @@ import { partHasRenderableText } from "../types/message"
interface MessagePartProps {
part: any
messageType?: "user" | "assistant"
}
export default function MessagePart(props: MessagePartProps) {
const { isDark } = useTheme()
const partType = () => props.part?.type || ""
const reasoningId = () => `reasoning-${props.part?.id || ""}`
const isReasoningExpanded = () => isItemExpanded(reasoningId())
const isAssistantMessage = () => props.messageType === "assistant"
const textContainerClass = () => (isAssistantMessage() ? "message-text message-text-assistant" : "message-text")
function handleReasoningClick(e: Event) {
e.preventDefault()
@@ -25,8 +27,8 @@ export default function MessagePart(props: MessagePartProps) {
<Switch>
<Match when={partType() === "text"}>
<Show when={!props.part.synthetic && partHasRenderableText(props.part)}>
<div class="message-text">
<Markdown part={props.part} isDark={isDark()} />
<div class={textContainerClass()}>
<Markdown part={props.part} isDark={isDark()} size={isAssistantMessage() ? "tight" : "base"} />
</div>
</Show>
</Match>
@@ -48,8 +50,8 @@ export default function MessagePart(props: MessagePartProps) {
<span class="reasoning-label">Reasoning</span>
</div>
<Show when={isReasoningExpanded()}>
<div class="message-text mt-2">
<Markdown part={props.part} isDark={isDark()} />
<div class={`${textContainerClass()} mt-2`}>
<Markdown part={props.part} isDark={isDark()} size={isAssistantMessage() ? "tight" : "base"} />
</div>
</Show>
</div>

View File

@@ -38,6 +38,6 @@
</head>
<body>
<div id="root"></div>
<script type="module" src="../main.tsx"></script>
<script type="module" src="./main.tsx"></script>
</body>
</html>

1
src/renderer/main.tsx Normal file
View File

@@ -0,0 +1 @@
import "../main.tsx"

View File

@@ -118,6 +118,11 @@ button.button-primary {
@apply flex flex-col gap-2 p-3 rounded-lg w-full;
}
.assistant-message {
/* gap: 0.25rem; */
padding: 0.6rem 0.65rem;
}
/* Message state badges */
.message-queued-badge {
@apply inline-block font-bold px-3 py-1 rounded mb-3 text-xs tracking-wide;
@@ -606,6 +611,7 @@ button.button-primary {
color: inherit;
}
.message-text pre {
overflow-x: auto;
padding: 8px;

View File

@@ -214,4 +214,108 @@
padding: 0 !important;
font-size: var(--font-size-xs) !important;
line-height: var(--line-height-normal);
}
}
/* Assistant message compact overrides */
.message-text-assistant {
font-size: 13.5px;
line-height: 1.1;
}
.message-text-assistant .prose {
font-size: 0.94rem;
}
.message-text-assistant .prose p {
margin: 0;
}
.message-text-assistant .prose ul {
margin: 0;
padding-left: 16px;
line-height: 1;
}
.message-text-assistant .prose ol {
margin: 0;
padding-left: 16px;
line-height: 1;
}
.message-text-assistant .prose ul + * {
margin-top: 8px;
}
.message-text-assistant .prose ul + ol {
margin-top: 4px;
}
.message-text-assistant .prose ol + * {
margin-top: 14px;
line-height: 1.05;
}
.message-text-assistant .prose ol + a,
.message-text-assistant .prose ol + code,
.message-text-assistant .prose ol + img {
margin-top: 14px !important;
display: inline-block;
}
.message-text-assistant .prose li {
margin: 0;
line-height: 1;
}
.message-text-assistant .prose li + li {
margin-top: 2px;
}
.message-text-assistant .prose li:last-child {
margin-bottom: 0;
padding-bottom: 0;
}
.message-text-assistant .prose li > br {
display: block;
margin: 0;
}
.message-text-assistant .prose li > br + a,
.message-text-assistant .prose li > br + code,
.message-text-assistant .prose li > br + img {
display: inline-block;
margin-top: 12px !important;
}
.message-text-assistant .prose h1 {
margin: 4px 0 2px;
font-size: 1.22em;
line-height: 1.15;
}
.message-text-assistant .prose h2 {
margin: 3px 0 2px;
font-size: 1.12em;
line-height: 1.15;
}
.message-text-assistant .prose h3 {
margin: 2px 0 2px;
font-size: 1.05em;
line-height: 1.15;
}
.message-text-assistant .prose blockquote {
margin: 6px 0 10px;
padding-left: 12px;
}
.message-text-assistant .prose blockquote p {
margin: 0;
}