fix(chat): render question prompts as markdown
This commit is contained in:
@@ -43,8 +43,12 @@ export const MarkdownRenderer: React.FC<React.ComponentPropsWithoutRef<typeof Ma
|
||||
</React.Suspense>
|
||||
);
|
||||
|
||||
export const SimpleMarkdownRenderer: React.FC<React.ComponentPropsWithoutRef<typeof SimpleMarkdownRendererLazy>> = (props) => (
|
||||
<React.Suspense fallback={<MobileMarkdownFallback {...props} />}>
|
||||
type SimpleMarkdownRendererProps = React.ComponentPropsWithoutRef<typeof SimpleMarkdownRendererLazy> & {
|
||||
fallbackContent?: React.ReactNode;
|
||||
};
|
||||
|
||||
export const SimpleMarkdownRenderer: React.FC<SimpleMarkdownRendererProps> = ({ fallbackContent, ...props }) => (
|
||||
<React.Suspense fallback={fallbackContent ?? <MobileMarkdownFallback {...props} />}>
|
||||
<SimpleMarkdownRendererLazy {...props} />
|
||||
</React.Suspense>
|
||||
);
|
||||
|
||||
@@ -15,6 +15,7 @@ import * as sessionActions from '@/sync/session-actions';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { serializeQuestionAsJson, serializeQuestionAsMarkdown } from './questionSerializers';
|
||||
import { QUESTION_CUSTOM_TEXTAREA_MIN_HEIGHT, getQuestionCustomTextareaHeight } from './questionTextareaSizing';
|
||||
import { QuestionMarkdown } from './QuestionMarkdown';
|
||||
|
||||
interface QuestionCardProps {
|
||||
question: QuestionRequest;
|
||||
@@ -423,7 +424,11 @@ export const QuestionCard: React.FC<QuestionCardProps> = ({ question }) => {
|
||||
</div>
|
||||
) : activeQuestion ? (
|
||||
<>
|
||||
<div className="typography-meta font-medium text-foreground mb-1.5">{activeQuestion.question}</div>
|
||||
<QuestionMarkdown
|
||||
content={activeQuestion.question}
|
||||
size="meta"
|
||||
className="font-medium text-foreground mb-1.5"
|
||||
/>
|
||||
|
||||
{isMultiple ? (
|
||||
<div className="typography-micro text-muted-foreground mb-1.5">{t('chat.questionCard.selectMultiple')}</div>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
|
||||
import { SimpleMarkdownRenderer } from './MarkdownRenderer';
|
||||
import { QuestionMarkdown } from './QuestionMarkdown';
|
||||
|
||||
describe('QuestionMarkdown', () => {
|
||||
test('delegates exact content to the tool markdown renderer', () => {
|
||||
const content = 'Choose **one** from `mode`: [details](https://example.com)';
|
||||
const element = QuestionMarkdown({ content, size: 'meta' });
|
||||
|
||||
expect(element.type).toBe(SimpleMarkdownRenderer);
|
||||
expect(element.props.content).toBe(content);
|
||||
expect(element.props.variant).toBe('tool');
|
||||
expect(element.props.fallbackContent.props.children).toBe(content);
|
||||
expect(element.props.fallbackContent.props.className).toContain('whitespace-pre-wrap');
|
||||
});
|
||||
|
||||
test('preserves question typography size and caller classes', () => {
|
||||
const meta = QuestionMarkdown({ content: 'Meta', size: 'meta', className: 'font-medium text-foreground' });
|
||||
const micro = QuestionMarkdown({ content: 'Micro', size: 'micro', className: 'text-muted-foreground' });
|
||||
|
||||
expect(meta.props.className).toBe('question-markdown typography-meta font-medium text-foreground');
|
||||
expect(micro.props.className).toBe('question-markdown typography-micro text-muted-foreground');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
import { SimpleMarkdownRenderer } from './MarkdownRenderer';
|
||||
|
||||
interface QuestionMarkdownProps {
|
||||
content: string;
|
||||
size: 'meta' | 'micro';
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function QuestionMarkdown({ content, size, className }: QuestionMarkdownProps) {
|
||||
const classes = cn('question-markdown', size === 'meta' ? 'typography-meta' : 'typography-micro', className);
|
||||
|
||||
return (
|
||||
<SimpleMarkdownRenderer
|
||||
content={content}
|
||||
variant="tool"
|
||||
className={classes}
|
||||
fallbackContent={<div className={cn(classes, 'whitespace-pre-wrap')}>{content}</div>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { RuntimeAPIContext } from '@/contexts/runtimeAPIContext';
|
||||
import { PatchDiff } from '@pierre/diffs/react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { SimpleMarkdownRenderer } from '../../MarkdownRenderer';
|
||||
import { QuestionMarkdown } from '../../QuestionMarkdown';
|
||||
import { MessageFilesDisplay } from '../../FileAttachment';
|
||||
import { getToolMetadata } from '@/lib/toolHelpers';
|
||||
import type { ToolPart as ToolPartType, ToolState as ToolStateUnion, FilePart } from '@opencode-ai/sdk/v2';
|
||||
@@ -1672,7 +1673,7 @@ const ToolExpandedContent: React.FC<ToolExpandedContentProps> = React.memo(({
|
||||
<div className="space-y-2">
|
||||
{parsedQA.map((qa, index) => (
|
||||
<div key={index} className="space-y-0.5">
|
||||
<div className="typography-micro text-muted-foreground">{qa.question}</div>
|
||||
<QuestionMarkdown content={qa.question} size="micro" className="text-muted-foreground" />
|
||||
<div className="typography-meta text-foreground whitespace-pre-wrap">{qa.answer}</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -1709,7 +1710,7 @@ const ToolExpandedContent: React.FC<ToolExpandedContentProps> = React.memo(({
|
||||
{q.header ? (
|
||||
<div className="typography-micro text-muted-foreground">{coerceToText(q.header)}</div>
|
||||
) : null}
|
||||
<div className="typography-meta text-foreground">{coerceToText(q.question)}</div>
|
||||
<QuestionMarkdown content={coerceToText(q.question)} size="meta" className="text-foreground" />
|
||||
{Array.isArray(q.options) && q.options.length > 0 ? (
|
||||
<div className="flex flex-wrap gap-1 mt-0.5">
|
||||
{q.options.map((opt) => (
|
||||
|
||||
@@ -1022,6 +1022,18 @@ html:not(.dark) .chat-scroll {
|
||||
font-size: var(--text-code) !important;
|
||||
}
|
||||
|
||||
.question-markdown > .markdown-content.markdown-tool {
|
||||
font-size: inherit !important;
|
||||
}
|
||||
|
||||
.question-markdown > .markdown-content > [data-md-block]:first-child > :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.question-markdown > .markdown-content > [data-md-block]:last-child > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Reasoning markdown renders at meta size, dimmed. */
|
||||
.markdown-content.markdown-reasoning {
|
||||
font-size: var(--text-markdown);
|
||||
|
||||
@@ -79,6 +79,10 @@
|
||||
font-size: var(--text-code) !important;
|
||||
}
|
||||
|
||||
:root.mobile-pointer:not(.desktop-runtime) .question-markdown > .markdown-content.markdown-tool {
|
||||
font-size: inherit !important;
|
||||
}
|
||||
|
||||
/* Improve touch targets for mobile */
|
||||
:root.mobile-pointer:not(.desktop-runtime) button:not([role="radio"]):not([role="checkbox"]):not([role="switch"]),
|
||||
:root.mobile-pointer:not(.desktop-runtime) .btn,
|
||||
|
||||
Reference in New Issue
Block a user