Skip to content

Commit 1a0a5fc

Browse files
Merge pull request #1373 from Ekep-Obasi/feat/formatting-for-hivechat-code-artifacts
feat: prettier formatting for hivechat code artifacts
2 parents 6d63d6e + 08d5c0d commit 1a0a5fc

File tree

8 files changed

+215
-28
lines changed

8 files changed

+215
-28
lines changed

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@
7171
"eslint-plugin-react": "^7.31.10",
7272
"formik": "^2.2.6",
7373
"fuse.js": "^5.2.3",
74-
"highlight.js": "^11.11.1",
7574
"husky": "^8.0.3",
7675
"jest-fetch-mock": "^3.0.3",
7776
"light-bolt11-decoder": "^3.0.0",
@@ -96,6 +95,7 @@
9695
"react-router-dom": "^5.2.0",
9796
"react-scripts": "5.0.1",
9897
"react-select": "^5.1.0",
98+
"react-syntax-highlighter": "^15.6.1",
9999
"recharts": "^2.15.1",
100100
"rehype-raw": "^6.1.1",
101101
"remark-gfm": "^3.0.1",
@@ -213,6 +213,7 @@
213213
]
214214
},
215215
"devDependencies": {
216+
"@types/react-syntax-highlighter": "^15.5.13",
216217
"cypress": "^13.6.3"
217218
}
218-
}
219+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { formatElapsedTime } from '../timeFormatting';
2+
3+
describe('formatElapsedTime', () => {
4+
const FIXED_TIMESTAMP = 1673352000000;
5+
6+
beforeEach(() => {
7+
jest.spyOn(global.Date, 'now').mockImplementation(() => FIXED_TIMESTAMP);
8+
9+
const actualDate = global.Date;
10+
global.Date = class extends actualDate {
11+
constructor(...args: any[]) {
12+
super();
13+
if (args.length === 0) {
14+
return new actualDate(FIXED_TIMESTAMP);
15+
}
16+
return new actualDate(...(args as [any?]));
17+
}
18+
} as unknown as DateConstructor;
19+
});
20+
21+
afterEach(() => {
22+
jest.restoreAllMocks();
23+
});
24+
25+
test('returns formatted time for normal case', () => {
26+
const firstAssignedAt = '2023-01-10T11:00:00Z';
27+
const result = formatElapsedTime(firstAssignedAt, null, false, null, 0);
28+
expect(result).toBe('01h 00m 00s');
29+
});
30+
31+
test('returns default time for invalid date', () => {
32+
const result = formatElapsedTime('invalid-date', null, false, null, 0);
33+
expect(result).toBe('00h 00m 00s');
34+
});
35+
36+
test('handles paused task with lastPowAt', () => {
37+
const firstAssignedAt = '2023-01-10T10:00:00Z'; // 2 hours ago
38+
const lastPowAt = '2023-01-10T11:30:00Z'; // 30 minutes ago
39+
const result = formatElapsedTime(firstAssignedAt, lastPowAt, true, null, 0);
40+
expect(result).toBe('01h 30m 00s');
41+
});
42+
43+
test('handles completed bounty', () => {
44+
const firstAssignedAt = '2023-01-10T10:00:00Z'; // 2 hours ago
45+
const lastPowAt = '2023-01-10T11:30:00Z'; // 30 minutes ago
46+
const closedAt = '2023-01-10T11:45:00Z'; // 15 minutes ago
47+
const result = formatElapsedTime(firstAssignedAt, lastPowAt, true, closedAt, 0);
48+
expect(result).toBe('01h 45m 00s');
49+
});
50+
51+
test('handles active task with lastPowAt', () => {
52+
const firstAssignedAt = '2023-01-10T10:00:00Z'; // 2 hours ago
53+
const lastPowAt = '2023-01-10T11:30:00Z'; // 30 minutes ago
54+
const result = formatElapsedTime(firstAssignedAt, lastPowAt, false, null, 0);
55+
expect(result).toBe('00h 30m 00s');
56+
});
57+
58+
test('subtracts accumulated pause seconds when paused', () => {
59+
const firstAssignedAt = '2023-01-10T11:00:00Z'; // 1 hour ago
60+
const accumulatedPauseSeconds = 1800; // 30 minutes
61+
const result = formatElapsedTime(firstAssignedAt, null, true, null, accumulatedPauseSeconds);
62+
expect(result).toBe('00h 30m 00s');
63+
});
64+
65+
test('handles edge case with negative time difference', () => {
66+
const futureDate = '2023-01-10T13:00:00Z'; // 1 hour in the future
67+
const result = formatElapsedTime(futureDate, null, false, null, 0);
68+
expect(result).toBe('00h 00m 00s');
69+
});
70+
});

src/helpers/codeFormatter.ts

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import prettier from 'prettier/standalone';
2+
import parserBabel from 'prettier/parser-babel';
3+
import parserHtml from 'prettier/parser-html';
4+
import parserPostcss from 'prettier/parser-postcss';
5+
import parserMarkdown from 'prettier/parser-markdown';
6+
7+
export const formatCodeWithPrettier = (code: string, language = 'javascript') => {
8+
try {
9+
const cleanedCode = code.trim();
10+
if (!cleanedCode) return code;
11+
12+
const parserMap = {
13+
js: { parser: 'babel', plugins: [parserBabel] },
14+
ts: { parser: 'babel', plugins: [parserBabel] },
15+
jsx: { parser: 'babel', plugins: [parserBabel] },
16+
tsx: { parser: 'babel', plugins: [parserBabel] },
17+
html: { parser: 'html', plugins: [parserHtml] },
18+
css: { parser: 'css', plugins: [parserPostcss] },
19+
md: { parser: 'markdown', plugins: [parserMarkdown] },
20+
json: { parser: 'json', plugins: [parserBabel] }
21+
};
22+
23+
const normalizedLang = language.toLowerCase().trim();
24+
25+
const config = parserMap[normalizedLang] || { parser: 'babel', plugins: [parserBabel] };
26+
27+
return prettier.format(cleanedCode, {
28+
parser: config.parser,
29+
plugins: config.plugins,
30+
semi: true,
31+
singleQuote: true,
32+
tabWidth: 2,
33+
printWidth: 80,
34+
trailingComma: 'es5'
35+
});
36+
} catch (error) {
37+
console.error(`Error formatting ${language} code with Prettier:`, error);
38+
return code;
39+
}
40+
};

src/people/hiveChat/index.tsx

+12-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import React, { useEffect, useState, useRef, useCallback } from 'react';
33
import { observer } from 'mobx-react-lite';
44
import { useParams } from 'react-router-dom';
5-
import hljs from 'highlight.js';
65
import { ChatMessage, Artifact, TextContent } from 'store/interface';
76
import { useStores } from 'store';
87
import { createSocketInstance } from 'config/socket';
@@ -15,6 +14,7 @@ import { chatHistoryStore } from 'store/chat.ts';
1514
import { renderMarkdown } from '../utils/RenderMarkdown.tsx';
1615
import { UploadModal } from '../../components/UploadModal';
1716
import { useFeatureFlag, useBrowserTabTitle } from '../../hooks';
17+
import { formatCodeWithPrettier } from '../../helpers/codeFormatter';
1818
import VisualScreenViewer from '../widgetViews/workspace/VisualScreenViewer.tsx';
1919
import { ModelOption } from './modelSelector.tsx';
2020
import { ActionArtifactRenderer } from './ActionArtifactRenderer';
@@ -501,8 +501,6 @@ const connectToLogWebSocket = (
501501
return ws;
502502
};
503503

504-
const highlightCode = (code: string): string => hljs.highlightAuto(code).value;
505-
506504
export const HiveChatView: React.FC = observer(() => {
507505
const { uuid, chatId } = useParams<RouteParams>();
508506
const { chat, ui } = useStores();
@@ -885,9 +883,18 @@ export const HiveChatView: React.FC = observer(() => {
885883
const isTextContent = (content: any): content is TextContent =>
886884
content && typeof content.text_type === 'string' && 'language' in content;
887885

888-
codeArtifacts.forEach((artifact) => {
886+
codeArtifacts.forEach(async (artifact) => {
889887
if (isTextContent(artifact.content)) {
890-
artifact.content.content = highlightCode(artifact.content.content);
888+
try {
889+
const language =
890+
(artifact.content as TextContent).code_metadata?.File.split('.').pop() || 'jsx';
891+
artifact.content.content = await formatCodeWithPrettier(
892+
artifact.content.content,
893+
language
894+
);
895+
} catch (error) {
896+
console.error('Failed to format code:', error);
897+
}
891898
}
892899
});
893900

src/people/utils/RenderMarkdown.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import remarkGfm from 'remark-gfm';
44
import rehypeRaw from 'rehype-raw';
55
import styled from 'styled-components';
66
import { colors } from '../../config';
7-
import 'highlight.js/styles/night-owl.css';
87

98
const MarkdownContainer = styled.div<{ textColor?: string }>`
109
font-size: 1rem;

src/people/widgetViews/workspace/Activities/Activities.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ const Activities = observer(() => {
416416
const [editedContent, setEditedContent] = useState('');
417417
const [toasts, setToasts] = useState<Toast[]>([]);
418418

419-
let interval: NodeJS.Timeout | null = null;
419+
let interval: NodeJS.Timeout | number | null = null;
420420

421421
useEffect(() => {
422422
if (uuid) {

src/people/widgetViews/workspace/VisualScreenViewer.tsx

+28-14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import React, { useState, useEffect, useRef } from 'react';
22
import styled from 'styled-components';
33
import MaterialIcon from '@material/react-material-icon';
4+
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
5+
import { coldarkDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
46
import { Artifact, VisualContent, TextContent } from '../../../store/interface.ts';
57
import { renderMarkdown } from '../../utils/RenderMarkdown.tsx';
68
import LogsScreenViewer from './LogsScreenViewer.tsx';
@@ -318,14 +320,23 @@ const VisualScreenViewer: React.FC<VisualScreenViewerProps> = ({
318320
}
319321
}, [activeTab, currentVisual]);
320322

321-
const copyCodeToClipboard = () => {
322-
if (currentCode?.content) {
323-
const content = (currentCode.content as TextContent).content || '';
324-
navigator.clipboard.writeText(content).then(() => {
325-
setCodeCopied(true);
326-
setTimeout(() => setCodeCopied(false), 2000);
327-
});
323+
const getCodeContent = (): { code: string; language: string } => {
324+
if (currentCode?.content && 'content' in currentCode.content) {
325+
const htmlContent = (currentCode.content as TextContent).content || '';
326+
327+
const language = (currentCode.content as TextContent).language?.toLowerCase() || 'javascript';
328+
329+
return { code: htmlContent, language };
328330
}
331+
return { code: '', language: 'javascript' };
332+
};
333+
334+
const copyCodeToClipboard = () => {
335+
const { code } = getCodeContent();
336+
navigator.clipboard.writeText(code).then(() => {
337+
setCodeCopied(true);
338+
setTimeout(() => setCodeCopied(false), 2000);
339+
});
329340
};
330341

331342
const copyTextToClipboard = () => {
@@ -436,13 +447,16 @@ const VisualScreenViewer: React.FC<VisualScreenViewerProps> = ({
436447
>
437448
{codeCopied ? <MaterialIcon icon="check" /> : <MaterialIcon icon="content_copy" />}
438449
</CopyButton>
439-
{renderMarkdown((currentCode.content as TextContent).content || '', {
440-
codeBlockBackground: '#282c34',
441-
textColor: '#abb2bf',
442-
bubbleTextColor: 'white',
443-
borderColor: '#444',
444-
codeBlockFont: 'Courier New'
445-
})}
450+
451+
<SyntaxHighlighter
452+
language={getCodeContent().language}
453+
style={coldarkDark}
454+
customStyle={{ background: '#1e1e1e', padding: '10px' }}
455+
wrapLines={true}
456+
wrapLongLines={true}
457+
>
458+
{getCodeContent().code}
459+
</SyntaxHighlighter>
446460
</CodeViewer>
447461
<PaginationControls>
448462
<Button onClick={handlePrevious} disabled={codeIndex === 0}>

yarn.lock

+61-5
Original file line numberDiff line numberDiff line change
@@ -1119,6 +1119,13 @@
11191119
dependencies:
11201120
regenerator-runtime "^0.14.0"
11211121

1122+
"@babel/runtime@^7.3.1":
1123+
version "7.27.0"
1124+
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.0.tgz#fbee7cf97c709518ecc1f590984481d5460d4762"
1125+
integrity sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==
1126+
dependencies:
1127+
regenerator-runtime "^0.14.0"
1128+
11221129
"@babel/template@^7.22.15", "@babel/template@^7.23.9", "@babel/template@^7.3.3":
11231130
version "7.23.9"
11241131
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.23.9.tgz#f881d0487cba2828d3259dcb9ef5005a9731011a"
@@ -2858,6 +2865,13 @@
28582865
"@types/history" "^4.7.11"
28592866
"@types/react" "*"
28602867

2868+
"@types/react-syntax-highlighter@^15.5.13":
2869+
version "15.5.13"
2870+
resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz#c5baf62a3219b3bf28d39cfea55d0a49a263d1f2"
2871+
integrity sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==
2872+
dependencies:
2873+
"@types/react" "*"
2874+
28612875
"@types/react-transition-group@^4.4.0":
28622876
version "4.4.10"
28632877
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.10.tgz#6ee71127bdab1f18f11ad8fb3322c6da27c327ac"
@@ -6081,6 +6095,13 @@ fastq@^1.6.0:
60816095
dependencies:
60826096
reusify "^1.0.4"
60836097

6098+
fault@^1.0.0:
6099+
version "1.0.4"
6100+
resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.4.tgz#eafcfc0a6d214fc94601e170df29954a4f842f13"
6101+
integrity sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==
6102+
dependencies:
6103+
format "^0.2.0"
6104+
60846105
faye-websocket@^0.11.3:
60856106
version "0.11.4"
60866107
resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da"
@@ -6300,6 +6321,11 @@ form-data@~2.3.2:
63006321
combined-stream "^1.0.6"
63016322
mime-types "^2.1.12"
63026323

6324+
format@^0.2.0:
6325+
version "0.2.2"
6326+
resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
6327+
integrity sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==
6328+
63036329
formik@^2.2.6:
63046330
version "2.4.5"
63056331
resolved "https://registry.yarnpkg.com/formik/-/formik-2.4.5.tgz#f899b5b7a6f103a8fabb679823e8fafc7e0ee1b4"
@@ -6808,10 +6834,15 @@ he@^1.2.0:
68086834
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
68096835
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
68106836

6811-
highlight.js@^11.11.1:
6812-
version "11.11.1"
6813-
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.11.1.tgz#fca06fa0e5aeecf6c4d437239135fabc15213585"
6814-
integrity sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==
6837+
highlight.js@^10.4.1, highlight.js@~10.7.0:
6838+
version "10.7.3"
6839+
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531"
6840+
integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==
6841+
6842+
highlightjs-vue@^1.0.0:
6843+
version "1.0.0"
6844+
resolved "https://registry.yarnpkg.com/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz#fdfe97fbea6354e70ee44e3a955875e114db086d"
6845+
integrity sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==
68156846

68166847
history@^4.9.0:
68176848
version "4.10.1"
@@ -8534,6 +8565,14 @@ lower-case@^2.0.2:
85348565
dependencies:
85358566
tslib "^2.0.3"
85368567

8568+
lowlight@^1.17.0:
8569+
version "1.20.0"
8570+
resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.20.0.tgz#ddb197d33462ad0d93bf19d17b6c301aa3941888"
8571+
integrity sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==
8572+
dependencies:
8573+
fault "^1.0.0"
8574+
highlight.js "~10.7.0"
8575+
85378576
lru-cache@^5.1.1:
85388577
version "5.1.1"
85398578
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
@@ -10394,6 +10433,11 @@ pretty-format@^29.0.0, pretty-format@^29.7.0:
1039410433
ansi-styles "^5.0.0"
1039510434
react-is "^18.0.0"
1039610435

10436+
prismjs@^1.27.0:
10437+
version "1.30.0"
10438+
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.30.0.tgz#d9709969d9d4e16403f6f348c63553b19f0975a9"
10439+
integrity sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==
10440+
1039710441
prismjs@~1.27.0:
1039810442
version "1.27.0"
1039910443
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057"
@@ -10926,6 +10970,18 @@ react-style-singleton@^2.2.0, react-style-singleton@^2.2.1:
1092610970
invariant "^2.2.4"
1092710971
tslib "^2.0.0"
1092810972

10973+
react-syntax-highlighter@^15.6.1:
10974+
version "15.6.1"
10975+
resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-15.6.1.tgz#fa567cb0a9f96be7bbccf2c13a3c4b5657d9543e"
10976+
integrity sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg==
10977+
dependencies:
10978+
"@babel/runtime" "^7.3.1"
10979+
highlight.js "^10.4.1"
10980+
highlightjs-vue "^1.0.0"
10981+
lowlight "^1.17.0"
10982+
prismjs "^1.27.0"
10983+
refractor "^3.6.0"
10984+
1092910985
react-transition-group@^4.3.0, react-transition-group@^4.4.5:
1093010986
version "4.4.5"
1093110987
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
@@ -11050,7 +11106,7 @@ reflect.getprototypeof@^1.0.4:
1105011106
globalthis "^1.0.3"
1105111107
which-builtin-type "^1.1.3"
1105211108

11053-
refractor@^3.5.0:
11109+
refractor@^3.5.0, refractor@^3.6.0:
1105411110
version "3.6.0"
1105511111
resolved "https://registry.yarnpkg.com/refractor/-/refractor-3.6.0.tgz#ac318f5a0715ead790fcfb0c71f4dd83d977935a"
1105611112
integrity sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==

0 commit comments

Comments
 (0)