more-compute 0.1.3__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- frontend/app/globals.css +322 -77
- frontend/app/layout.tsx +98 -82
- frontend/components/Cell.tsx +234 -95
- frontend/components/Notebook.tsx +430 -199
- frontend/components/{AddCellButton.tsx → cell/AddCellButton.tsx} +0 -2
- frontend/components/cell/MonacoCell.tsx +726 -0
- frontend/components/layout/ConnectionBanner.tsx +41 -0
- frontend/components/{Sidebar.tsx → layout/Sidebar.tsx} +16 -11
- frontend/components/modals/ConfirmModal.tsx +154 -0
- frontend/components/modals/SuccessModal.tsx +140 -0
- frontend/components/output/MarkdownRenderer.tsx +116 -0
- frontend/components/popups/ComputePopup.tsx +674 -365
- frontend/components/popups/MetricsPopup.tsx +11 -7
- frontend/components/popups/SettingsPopup.tsx +11 -13
- frontend/contexts/PodWebSocketContext.tsx +247 -0
- frontend/eslint.config.mjs +11 -0
- frontend/lib/monaco-themes.ts +160 -0
- frontend/lib/settings.ts +128 -26
- frontend/lib/themes.json +9973 -0
- frontend/lib/websocket-native.ts +19 -8
- frontend/lib/websocket.ts +59 -11
- frontend/next.config.ts +8 -0
- frontend/package-lock.json +1705 -3
- frontend/package.json +8 -1
- frontend/styling_README.md +18 -0
- kernel_run.py +161 -43
- more_compute-0.2.0.dist-info/METADATA +126 -0
- more_compute-0.2.0.dist-info/RECORD +100 -0
- morecompute/__version__.py +1 -0
- morecompute/execution/executor.py +31 -20
- morecompute/execution/worker.py +68 -7
- morecompute/models/__init__.py +31 -0
- morecompute/models/api_models.py +197 -0
- morecompute/notebook.py +50 -7
- morecompute/server.py +574 -94
- morecompute/services/data_manager.py +379 -0
- morecompute/services/lsp_service.py +335 -0
- morecompute/services/pod_manager.py +122 -20
- morecompute/services/pod_monitor.py +138 -0
- morecompute/services/prime_intellect.py +87 -63
- morecompute/utils/config_util.py +59 -0
- morecompute/utils/special_commands.py +11 -5
- morecompute/utils/zmq_util.py +51 -0
- frontend/components/MarkdownRenderer.tsx +0 -84
- frontend/components/popups/PythonPopup.tsx +0 -292
- more_compute-0.1.3.dist-info/METADATA +0 -173
- more_compute-0.1.3.dist-info/RECORD +0 -85
- /frontend/components/{CellButton.tsx → cell/CellButton.tsx} +0 -0
- /frontend/components/{ErrorModal.tsx → modals/ErrorModal.tsx} +0 -0
- /frontend/components/{CellOutput.tsx → output/CellOutput.tsx} +0 -0
- /frontend/components/{ErrorDisplay.tsx → output/ErrorDisplay.tsx} +0 -0
- {more_compute-0.1.3.dist-info → more_compute-0.2.0.dist-info}/WHEEL +0 -0
- {more_compute-0.1.3.dist-info → more_compute-0.2.0.dist-info}/entry_points.txt +0 -0
- {more_compute-0.1.3.dist-info → more_compute-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {more_compute-0.1.3.dist-info → more_compute-0.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import {
|
|
5
|
+
usePodWebSocket,
|
|
6
|
+
ConnectionState,
|
|
7
|
+
} from "@/contexts/PodWebSocketContext";
|
|
8
|
+
|
|
9
|
+
interface ConnectionBannerProps {
|
|
10
|
+
connectionState: ConnectionState;
|
|
11
|
+
podName?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const ConnectionBanner: React.FC<ConnectionBannerProps> = ({
|
|
15
|
+
connectionState,
|
|
16
|
+
podName,
|
|
17
|
+
}) => {
|
|
18
|
+
if (!connectionState) return null;
|
|
19
|
+
|
|
20
|
+
const getMessage = () => {
|
|
21
|
+
switch (connectionState) {
|
|
22
|
+
case "provisioning":
|
|
23
|
+
return "PROVISIONING GPU";
|
|
24
|
+
case "deploying":
|
|
25
|
+
return "Deploying worker...";
|
|
26
|
+
case "connected":
|
|
27
|
+
return "Connected!";
|
|
28
|
+
default:
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const message = getMessage();
|
|
34
|
+
if (!message) return null;
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className={`connection-banner ${connectionState}`}>
|
|
38
|
+
<span className="connection-status-text">{message}</span>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Folder, Package, Cpu, Settings, ChartArea
|
|
2
|
+
import { Folder, Package, Cpu, Settings, ChartArea } from "lucide-react";
|
|
3
3
|
|
|
4
4
|
interface SidebarItemData {
|
|
5
5
|
id: string;
|
|
@@ -8,16 +8,11 @@ interface SidebarItemData {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
const sidebarItems: SidebarItemData[] = [
|
|
11
|
-
{ id: "folder", icon: <Folder size={
|
|
12
|
-
{ id: "packages", icon: <Package size={
|
|
13
|
-
{
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
tooltip: "Python",
|
|
17
|
-
},
|
|
18
|
-
{ id: "compute", icon: <Cpu size={18} />, tooltip: "Compute" },
|
|
19
|
-
{ id: "metrics", icon: <ChartArea size={18} />, tooltip: "Metrics" },
|
|
20
|
-
{ id: "settings", icon: <Settings size={18} />, tooltip: "Settings" },
|
|
11
|
+
{ id: "folder", icon: <Folder size={16} />, tooltip: "Files" },
|
|
12
|
+
{ id: "packages", icon: <Package size={16} />, tooltip: "Packages" },
|
|
13
|
+
{ id: "compute", icon: <Cpu size={16} />, tooltip: "Compute" },
|
|
14
|
+
{ id: "metrics", icon: <ChartArea size={16} />, tooltip: "Metrics" },
|
|
15
|
+
{ id: "settings", icon: <Settings size={16} />, tooltip: "Settings" },
|
|
21
16
|
];
|
|
22
17
|
|
|
23
18
|
interface SidebarProps {
|
|
@@ -26,8 +21,18 @@ interface SidebarProps {
|
|
|
26
21
|
}
|
|
27
22
|
|
|
28
23
|
const Sidebar: React.FC<SidebarProps> = ({ onTogglePopup, activePopup }) => {
|
|
24
|
+
const activeIndex = sidebarItems.findIndex((item) => item.id === activePopup);
|
|
25
|
+
|
|
29
26
|
return (
|
|
30
27
|
<div id="sidebar" className="sidebar">
|
|
28
|
+
{activeIndex !== -1 && (
|
|
29
|
+
<div
|
|
30
|
+
className="sidebar-active-indicator"
|
|
31
|
+
style={{
|
|
32
|
+
transform: `translateY(${activeIndex * 44}px)`,
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
35
|
+
)}
|
|
31
36
|
{sidebarItems.map((item) => (
|
|
32
37
|
<div
|
|
33
38
|
key={item.id}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { AlertTriangle, X } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
interface ConfirmModalProps {
|
|
5
|
+
isOpen: boolean;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
onConfirm: () => void;
|
|
8
|
+
title: string;
|
|
9
|
+
message: string;
|
|
10
|
+
confirmLabel?: string;
|
|
11
|
+
cancelLabel?: string;
|
|
12
|
+
isDangerous?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const ConfirmModal: React.FC<ConfirmModalProps> = ({
|
|
16
|
+
isOpen,
|
|
17
|
+
onClose,
|
|
18
|
+
onConfirm,
|
|
19
|
+
title,
|
|
20
|
+
message,
|
|
21
|
+
confirmLabel = "Confirm",
|
|
22
|
+
cancelLabel = "Cancel",
|
|
23
|
+
isDangerous = false,
|
|
24
|
+
}) => {
|
|
25
|
+
if (!isOpen) return null;
|
|
26
|
+
|
|
27
|
+
const handleConfirm = () => {
|
|
28
|
+
onConfirm();
|
|
29
|
+
onClose();
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
style={{
|
|
35
|
+
position: "fixed",
|
|
36
|
+
top: 0,
|
|
37
|
+
left: 0,
|
|
38
|
+
right: 0,
|
|
39
|
+
bottom: 0,
|
|
40
|
+
backgroundColor: "rgba(0, 0, 0, 0.4)",
|
|
41
|
+
display: "flex",
|
|
42
|
+
alignItems: "center",
|
|
43
|
+
justifyContent: "center",
|
|
44
|
+
zIndex: 10000,
|
|
45
|
+
}}
|
|
46
|
+
onClick={onClose}
|
|
47
|
+
>
|
|
48
|
+
<div
|
|
49
|
+
style={{
|
|
50
|
+
backgroundColor: "#1a1a1a",
|
|
51
|
+
borderRadius: "8px",
|
|
52
|
+
padding: "24px",
|
|
53
|
+
maxWidth: "500px",
|
|
54
|
+
width: "90%",
|
|
55
|
+
maxHeight: "80vh",
|
|
56
|
+
overflow: "auto",
|
|
57
|
+
border: "2px solid #444",
|
|
58
|
+
boxShadow: "0 8px 32px rgba(0, 0, 0, 0.8)",
|
|
59
|
+
}}
|
|
60
|
+
onClick={(e) => e.stopPropagation()}
|
|
61
|
+
>
|
|
62
|
+
<div
|
|
63
|
+
style={{
|
|
64
|
+
display: "flex",
|
|
65
|
+
justifyContent: "space-between",
|
|
66
|
+
alignItems: "center",
|
|
67
|
+
marginBottom: "16px",
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
|
71
|
+
{isDangerous && <AlertTriangle size={20} color="#ffc107" />}
|
|
72
|
+
<h2
|
|
73
|
+
style={{
|
|
74
|
+
margin: 0,
|
|
75
|
+
fontSize: "18px",
|
|
76
|
+
fontWeight: 600,
|
|
77
|
+
color: isDangerous ? "#ffc107" : "#e0e0e0",
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
{title}
|
|
81
|
+
</h2>
|
|
82
|
+
</div>
|
|
83
|
+
<button
|
|
84
|
+
onClick={onClose}
|
|
85
|
+
style={{
|
|
86
|
+
background: "none",
|
|
87
|
+
border: "none",
|
|
88
|
+
cursor: "pointer",
|
|
89
|
+
padding: "4px",
|
|
90
|
+
display: "flex",
|
|
91
|
+
alignItems: "center",
|
|
92
|
+
color: "#999",
|
|
93
|
+
}}
|
|
94
|
+
>
|
|
95
|
+
<X size={20} />
|
|
96
|
+
</button>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<div
|
|
100
|
+
style={{
|
|
101
|
+
marginBottom: "20px",
|
|
102
|
+
fontSize: "14px",
|
|
103
|
+
lineHeight: "1.6",
|
|
104
|
+
whiteSpace: "pre-line",
|
|
105
|
+
color: "#e0e0e0",
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
{message}
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div
|
|
112
|
+
style={{
|
|
113
|
+
display: "flex",
|
|
114
|
+
gap: "8px",
|
|
115
|
+
justifyContent: "flex-end",
|
|
116
|
+
}}
|
|
117
|
+
>
|
|
118
|
+
<button
|
|
119
|
+
onClick={onClose}
|
|
120
|
+
style={{
|
|
121
|
+
padding: "10px 20px",
|
|
122
|
+
backgroundColor: "#2a2a2a",
|
|
123
|
+
color: "#e0e0e0",
|
|
124
|
+
border: "1px solid #444",
|
|
125
|
+
borderRadius: "4px",
|
|
126
|
+
cursor: "pointer",
|
|
127
|
+
fontSize: "14px",
|
|
128
|
+
fontWeight: 500,
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
{cancelLabel}
|
|
132
|
+
</button>
|
|
133
|
+
<button
|
|
134
|
+
onClick={handleConfirm}
|
|
135
|
+
style={{
|
|
136
|
+
padding: "10px 20px",
|
|
137
|
+
backgroundColor: isDangerous ? "#dc2626" : "#4a9eff",
|
|
138
|
+
color: "white",
|
|
139
|
+
border: "none",
|
|
140
|
+
borderRadius: "4px",
|
|
141
|
+
cursor: "pointer",
|
|
142
|
+
fontSize: "14px",
|
|
143
|
+
fontWeight: 500,
|
|
144
|
+
}}
|
|
145
|
+
>
|
|
146
|
+
{confirmLabel}
|
|
147
|
+
</button>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export default ConfirmModal;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { CheckCircle2, X } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
interface SuccessModalProps {
|
|
5
|
+
isOpen: boolean;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
title: string;
|
|
8
|
+
message: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const SuccessModal: React.FC<SuccessModalProps> = ({
|
|
12
|
+
isOpen,
|
|
13
|
+
onClose,
|
|
14
|
+
title,
|
|
15
|
+
message,
|
|
16
|
+
}) => {
|
|
17
|
+
if (!isOpen) return null;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
style={{
|
|
22
|
+
position: "fixed",
|
|
23
|
+
top: 0,
|
|
24
|
+
left: 0,
|
|
25
|
+
right: 0,
|
|
26
|
+
bottom: 0,
|
|
27
|
+
backgroundColor: "rgba(0, 0, 0, 0.4)",
|
|
28
|
+
display: "flex",
|
|
29
|
+
alignItems: "center",
|
|
30
|
+
justifyContent: "center",
|
|
31
|
+
zIndex: 10000,
|
|
32
|
+
}}
|
|
33
|
+
onClick={onClose}
|
|
34
|
+
>
|
|
35
|
+
<div
|
|
36
|
+
style={{
|
|
37
|
+
backgroundColor: "#1a1a1a",
|
|
38
|
+
borderRadius: "8px",
|
|
39
|
+
padding: "24px",
|
|
40
|
+
maxWidth: "500px",
|
|
41
|
+
width: "90%",
|
|
42
|
+
border: "2px solid #444",
|
|
43
|
+
boxShadow: "0 8px 32px rgba(0, 0, 0, 0.8)",
|
|
44
|
+
}}
|
|
45
|
+
onClick={(e) => e.stopPropagation()}
|
|
46
|
+
>
|
|
47
|
+
<div
|
|
48
|
+
style={{
|
|
49
|
+
display: "flex",
|
|
50
|
+
justifyContent: "space-between",
|
|
51
|
+
alignItems: "center",
|
|
52
|
+
marginBottom: "16px",
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
<div
|
|
56
|
+
style={{
|
|
57
|
+
display: "flex",
|
|
58
|
+
alignItems: "center",
|
|
59
|
+
gap: "8px",
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
<CheckCircle2
|
|
63
|
+
size={20}
|
|
64
|
+
style={{
|
|
65
|
+
color: "#10b981",
|
|
66
|
+
}}
|
|
67
|
+
/>
|
|
68
|
+
<h2
|
|
69
|
+
style={{
|
|
70
|
+
margin: 0,
|
|
71
|
+
fontSize: "18px",
|
|
72
|
+
fontWeight: 600,
|
|
73
|
+
color: "#10b981",
|
|
74
|
+
fontFamily:
|
|
75
|
+
"'Fira', 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace",
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
{title}
|
|
79
|
+
</h2>
|
|
80
|
+
</div>
|
|
81
|
+
<button
|
|
82
|
+
onClick={onClose}
|
|
83
|
+
style={{
|
|
84
|
+
background: "none",
|
|
85
|
+
border: "none",
|
|
86
|
+
cursor: "pointer",
|
|
87
|
+
padding: "4px",
|
|
88
|
+
display: "flex",
|
|
89
|
+
alignItems: "center",
|
|
90
|
+
color: "#999",
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
93
|
+
<X size={20} />
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<div
|
|
98
|
+
style={{
|
|
99
|
+
marginBottom: "20px",
|
|
100
|
+
fontSize: "14px",
|
|
101
|
+
lineHeight: "1.6",
|
|
102
|
+
whiteSpace: "pre-line",
|
|
103
|
+
color: "#e0e0e0",
|
|
104
|
+
fontFamily:
|
|
105
|
+
"'Fira', 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace",
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
{message}
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div
|
|
112
|
+
style={{
|
|
113
|
+
display: "flex",
|
|
114
|
+
justifyContent: "flex-end",
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
<button
|
|
118
|
+
onClick={onClose}
|
|
119
|
+
style={{
|
|
120
|
+
padding: "10px 20px",
|
|
121
|
+
backgroundColor: "#000",
|
|
122
|
+
color: "#fff",
|
|
123
|
+
border: "1px solid #444",
|
|
124
|
+
borderRadius: "8px",
|
|
125
|
+
cursor: "pointer",
|
|
126
|
+
fontSize: "14px",
|
|
127
|
+
fontWeight: 500,
|
|
128
|
+
fontFamily:
|
|
129
|
+
"'Fira', 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace",
|
|
130
|
+
}}
|
|
131
|
+
>
|
|
132
|
+
OK
|
|
133
|
+
</button>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export default SuccessModal;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
|
|
5
|
+
interface MarkdownRendererProps {
|
|
6
|
+
source: string;
|
|
7
|
+
onClick?: () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
|
|
11
|
+
source,
|
|
12
|
+
onClick,
|
|
13
|
+
}) => {
|
|
14
|
+
if (!source || !source.trim()) {
|
|
15
|
+
return (
|
|
16
|
+
<div
|
|
17
|
+
className="markdown-rendered markdown-empty"
|
|
18
|
+
onClick={onClick}
|
|
19
|
+
style={{
|
|
20
|
+
minHeight: "2.5rem",
|
|
21
|
+
padding: "0.5rem",
|
|
22
|
+
color: "#9ca3af",
|
|
23
|
+
fontStyle: "italic",
|
|
24
|
+
cursor: "pointer",
|
|
25
|
+
}}
|
|
26
|
+
></div>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const escapeHtml = (text: string) => {
|
|
31
|
+
const div = document.createElement("div");
|
|
32
|
+
div.textContent = text;
|
|
33
|
+
return div.innerHTML;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const renderMarkdown = (text: string) => {
|
|
37
|
+
let html = text;
|
|
38
|
+
|
|
39
|
+
// Code blocks (must be processed first)
|
|
40
|
+
html = html.replace(/```([\s\S]*?)```/g, (match, code) => {
|
|
41
|
+
return `<pre><code>${escapeHtml(code.trim())}</code></pre>`;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Language-specific code blocks
|
|
45
|
+
html = html.replace(/```(\w+)\n([\s\S]*?)```/g, (match, lang, code) => {
|
|
46
|
+
return `<pre><code class="language-${lang}">${escapeHtml(code.trim())}</code></pre>`;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Headers
|
|
50
|
+
html = html.replace(/^### (.*$)/gim, "<h3>$1</h3>");
|
|
51
|
+
html = html.replace(/^## (.*$)/gim, "<h2>$1</h2>");
|
|
52
|
+
html = html.replace(/^# (.*$)/gim, "<h1>$1</h1>");
|
|
53
|
+
|
|
54
|
+
// Bold and Italic
|
|
55
|
+
html = html.replace(/\*\*\*([^*]+)\*\*\*/g, "<strong><em>$1</em></strong>");
|
|
56
|
+
html = html.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
|
|
57
|
+
html = html.replace(/\*([^*]+)\*/g, "<em>$1</em>");
|
|
58
|
+
|
|
59
|
+
// Strikethrough
|
|
60
|
+
html = html.replace(/~~([^~]+)~~/g, "<del>$1</del>");
|
|
61
|
+
|
|
62
|
+
// Links
|
|
63
|
+
html = html.replace(
|
|
64
|
+
/\[([^\]]+)\]\(([^)]+)\)/g,
|
|
65
|
+
'<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>',
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// Unordered lists
|
|
69
|
+
html = html.replace(
|
|
70
|
+
/^\s*[-*+] (.+)$/gim,
|
|
71
|
+
(match, item) => `<li>${item}</li>`,
|
|
72
|
+
);
|
|
73
|
+
html = html
|
|
74
|
+
.replace(/(<li>[\s\S]*?<\/li>)/g, "<ul>$1</ul>")
|
|
75
|
+
.replace(/<\/ul>\s*<ul>/g, "");
|
|
76
|
+
|
|
77
|
+
// Ordered lists
|
|
78
|
+
html = html
|
|
79
|
+
.replace(
|
|
80
|
+
/^\s*\d+\. (.+)$/gim,
|
|
81
|
+
(match, item) => `<ol><li>${item}</li></ol>`,
|
|
82
|
+
)
|
|
83
|
+
.replace(/<\/ol>\s*<ol>/g, "");
|
|
84
|
+
|
|
85
|
+
// Inline code
|
|
86
|
+
html = html.replace(/`([^`]+)`/g, "<code>$1</code>");
|
|
87
|
+
|
|
88
|
+
// Blockquotes
|
|
89
|
+
html = html.replace(/^> (.+)$/gim, "<blockquote>$1</blockquote>");
|
|
90
|
+
|
|
91
|
+
// Handle HTML br tags first - convert them to proper line breaks
|
|
92
|
+
html = html.replace(/<br\s*\/?>/gi, "\n");
|
|
93
|
+
|
|
94
|
+
// Line breaks and paragraphs - handle paragraph separation properly
|
|
95
|
+
// Split by double newlines to create paragraphs, but preserve single line breaks within paragraphs
|
|
96
|
+
html = html.replace(/\n\s*\n/g, "</p><p>"); // Paragraph breaks (double newlines with optional whitespace)
|
|
97
|
+
html = html.replace(/\n(?!<\/p>)/g, "<br>"); // Line breaks within paragraphs (but not paragraph breaks)
|
|
98
|
+
|
|
99
|
+
// Ensure content is wrapped in paragraph tags if not already in a block element
|
|
100
|
+
if (!html.match(/^<(h[1-6]|ul|ol|pre|blockquote|hr|p)/) && html.trim()) {
|
|
101
|
+
html = "<p>" + html + "</p>";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return html;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<div
|
|
109
|
+
className="markdown-rendered"
|
|
110
|
+
onClick={onClick}
|
|
111
|
+
dangerouslySetInnerHTML={{ __html: renderMarkdown(source) }}
|
|
112
|
+
/>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export default MarkdownRenderer;
|