Skip to content

Commit d5c6bc7

Browse files
committed
Add image preview support
1 parent f3cab99 commit d5c6bc7

10 files changed

Lines changed: 378 additions & 11 deletions

components/PreviewPane.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const PreviewPane = React.forwardRef<HTMLDivElement, PreviewPaneProps>(({ conten
2828

2929
setError(null);
3030
const renderer = previewService.getRendererForLanguage(language);
31-
const result = await renderer.render(content, addLog);
31+
const result = await renderer.render(content, addLog, language);
3232

3333
clearTimeout(loadingTimer);
3434
if (!isCancelled) {

components/PromptEditor.tsx

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,30 @@ interface DocumentEditorProps {
2626
formatTrigger: number;
2727
}
2828

29+
const PREVIEWABLE_LANGUAGES = new Set<string>([
30+
'markdown',
31+
'html',
32+
'pdf',
33+
'application/pdf',
34+
'image',
35+
'png',
36+
'jpg',
37+
'jpeg',
38+
'gif',
39+
'bmp',
40+
'webp',
41+
'svg',
42+
'svg+xml',
43+
'image/png',
44+
'image/jpg',
45+
'image/jpeg',
46+
'image/gif',
47+
'image/webp',
48+
'image/bmp',
49+
'image/svg',
50+
'image/svg+xml',
51+
]);
52+
2953
const resolveDefaultViewMode = (mode: ViewMode | null | undefined, languageHint: string | null | undefined): ViewMode => {
3054
if (mode) return mode;
3155
const normalizedHint = languageHint?.toLowerCase();
@@ -357,7 +381,7 @@ const DocumentEditor: React.FC<DocumentEditorProps> = ({ documentNode, onSave, o
357381
}
358382
setRefinedContent(null);
359383
};
360-
384+
361385
const handleCopy = async () => {
362386
if (!content.trim()) return;
363387
await navigator.clipboard.writeText(content);
@@ -367,10 +391,11 @@ const DocumentEditor: React.FC<DocumentEditorProps> = ({ documentNode, onSave, o
367391
};
368392

369393
const language = documentNode.language_hint || 'plaintext';
370-
const supportsAiTools = ['markdown', 'plaintext'].includes(language);
371-
const supportsPreview = ['markdown', 'html', 'pdf'].includes(language);
372-
const supportsFormatting = ['javascript', 'typescript', 'json', 'html', 'css', 'xml', 'yaml'].includes(language);
373-
const isPythonDocument = typeof window !== 'undefined' && !!window.electronAPI && (language === 'python');
394+
const normalizedLanguage = language.toLowerCase();
395+
const supportsAiTools = ['markdown', 'plaintext'].includes(normalizedLanguage);
396+
const supportsPreview = PREVIEWABLE_LANGUAGES.has(normalizedLanguage);
397+
const supportsFormatting = ['javascript', 'typescript', 'json', 'html', 'css', 'xml', 'yaml'].includes(normalizedLanguage);
398+
const isPythonDocument = typeof window !== 'undefined' && !!window.electronAPI && (normalizedLanguage === 'python');
374399
const pythonDefaults = useMemo(() => ({
375400
...settings.pythonDefaults,
376401
workingDirectory: settings.pythonWorkingDirectory ?? settings.pythonDefaults.workingDirectory ?? null,

services/languageService.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const SUPPORTED_LANGUAGES = [
2222
{ id: 'pascal', label: 'Pascal' },
2323
{ id: 'ini', label: 'INI' },
2424
{ id: 'pdf', label: 'PDF' },
25+
{ id: 'image', label: 'Image (PNG/JPEG/GIF/WebP/SVG/BMP)' },
2526
];
2627

2728
export const mapExtensionToLanguageId = (extension: string | null): string => {
@@ -80,6 +81,23 @@ export const mapExtensionToLanguageId = (extension: string | null): string => {
8081
return 'pdf';
8182
case 'pdf':
8283
return 'pdf';
84+
case 'png':
85+
case 'jpg':
86+
case 'jpeg':
87+
case 'gif':
88+
case 'bmp':
89+
case 'webp':
90+
case 'svg':
91+
return 'image';
92+
case 'image/png':
93+
case 'image/jpg':
94+
case 'image/jpeg':
95+
case 'image/gif':
96+
case 'image/bmp':
97+
case 'image/webp':
98+
case 'image/svg':
99+
case 'image/svg+xml':
100+
return 'image';
83101
default:
84102
// Try to find a direct match in supported languages by id
85103
const match = SUPPORTED_LANGUAGES.find(l => l.id === extension.toLowerCase());

services/preview/IRenderer.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,9 @@ export interface IRenderer {
1010
/**
1111
* Takes a string of content and transforms it into a renderable React element or HTML string.
1212
*/
13-
render(content: string, addLog?: (level: LogLevel, message: string) => void): Promise<{ output: React.ReactElement | string; error?: string }>;
13+
render(
14+
content: string,
15+
addLog?: (level: LogLevel, message: string) => void,
16+
languageId?: string | null,
17+
): Promise<{ output: React.ReactElement | string; error?: string }>;
1418
}

services/preview/htmlRenderer.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ export class HtmlRenderer implements IRenderer {
77
return languageId === 'html';
88
}
99

10-
async render(content: string, addLog?: (level: LogLevel, message: string) => void): Promise<{ output: React.ReactElement; error?: string }> {
10+
async render(
11+
content: string,
12+
addLog?: (level: LogLevel, message: string) => void,
13+
languageId?: string | null,
14+
): Promise<{ output: React.ReactElement; error?: string }> {
1115
// Using `color-scheme` allows the iframe content to respect the system's light/dark mode preference.
1216
const fullHtml = `<html><head><style>body { color-scheme: light dark; font-family: sans-serif; padding: 1rem; }</style></head><body>${content}</body></html>`;
1317

0 commit comments

Comments
 (0)