@ryanfw/prompt-orchestration-pipeline 0.11.0 → 0.13.0
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.
- package/package.json +11 -1
- package/src/cli/analyze-task.js +51 -0
- package/src/cli/index.js +8 -0
- package/src/components/AddPipelineSidebar.jsx +144 -0
- package/src/components/AnalysisProgressTray.jsx +87 -0
- package/src/components/DAGGrid.jsx +157 -47
- package/src/components/JobTable.jsx +4 -3
- package/src/components/Layout.jsx +142 -139
- package/src/components/MarkdownRenderer.jsx +149 -0
- package/src/components/PipelineDAGGrid.jsx +404 -0
- package/src/components/PipelineTypeTaskSidebar.jsx +96 -0
- package/src/components/SchemaPreviewPanel.jsx +97 -0
- package/src/components/StageTimeline.jsx +36 -0
- package/src/components/TaskAnalysisDisplay.jsx +227 -0
- package/src/components/TaskCreationSidebar.jsx +447 -0
- package/src/components/TaskDetailSidebar.jsx +119 -117
- package/src/components/TaskFilePane.jsx +94 -39
- package/src/components/ui/RestartJobModal.jsx +26 -6
- package/src/components/ui/StopJobModal.jsx +183 -0
- package/src/components/ui/button.jsx +59 -27
- package/src/components/ui/sidebar.jsx +118 -0
- package/src/config/models.js +99 -67
- package/src/core/config.js +11 -4
- package/src/core/lifecycle-policy.js +62 -0
- package/src/core/pipeline-runner.js +312 -217
- package/src/core/status-writer.js +84 -0
- package/src/llm/index.js +129 -9
- package/src/pages/Code.jsx +8 -1
- package/src/pages/PipelineDetail.jsx +84 -2
- package/src/pages/PipelineList.jsx +214 -0
- package/src/pages/PipelineTypeDetail.jsx +234 -0
- package/src/pages/PromptPipelineDashboard.jsx +10 -11
- package/src/providers/deepseek.js +76 -16
- package/src/providers/openai.js +61 -34
- package/src/task-analysis/enrichers/analysis-writer.js +62 -0
- package/src/task-analysis/enrichers/schema-deducer.js +145 -0
- package/src/task-analysis/enrichers/schema-writer.js +74 -0
- package/src/task-analysis/extractors/artifacts.js +137 -0
- package/src/task-analysis/extractors/llm-calls.js +176 -0
- package/src/task-analysis/extractors/stages.js +51 -0
- package/src/task-analysis/index.js +103 -0
- package/src/task-analysis/parser.js +28 -0
- package/src/task-analysis/utils/ast.js +43 -0
- package/src/ui/client/adapters/job-adapter.js +60 -0
- package/src/ui/client/api.js +233 -8
- package/src/ui/client/hooks/useAnalysisProgress.js +145 -0
- package/src/ui/client/hooks/useJobList.js +14 -1
- package/src/ui/client/index.css +64 -0
- package/src/ui/client/main.jsx +4 -0
- package/src/ui/client/sse-fetch.js +120 -0
- package/src/ui/dist/app.js +262 -0
- package/src/ui/dist/assets/index-cjHV9mYW.js +82578 -0
- package/src/ui/dist/assets/index-cjHV9mYW.js.map +1 -0
- package/src/ui/dist/assets/style-CoM9SoQF.css +180 -0
- package/src/ui/dist/favicon.svg +12 -0
- package/src/ui/dist/index.html +2 -2
- package/src/ui/endpoints/create-pipeline-endpoint.js +194 -0
- package/src/ui/endpoints/file-endpoints.js +330 -0
- package/src/ui/endpoints/job-control-endpoints.js +1001 -0
- package/src/ui/endpoints/job-endpoints.js +62 -0
- package/src/ui/endpoints/pipeline-analysis-endpoint.js +246 -0
- package/src/ui/endpoints/pipeline-type-detail-endpoint.js +181 -0
- package/src/ui/endpoints/pipelines-endpoint.js +133 -0
- package/src/ui/endpoints/schema-file-endpoint.js +105 -0
- package/src/ui/endpoints/sse-endpoints.js +223 -0
- package/src/ui/endpoints/state-endpoint.js +85 -0
- package/src/ui/endpoints/task-analysis-endpoint.js +104 -0
- package/src/ui/endpoints/task-creation-endpoint.js +114 -0
- package/src/ui/endpoints/task-save-endpoint.js +101 -0
- package/src/ui/endpoints/upload-endpoints.js +406 -0
- package/src/ui/express-app.js +227 -0
- package/src/ui/lib/analysis-lock.js +67 -0
- package/src/ui/lib/sse.js +30 -0
- package/src/ui/server.js +42 -1880
- package/src/ui/sse-broadcast.js +93 -0
- package/src/ui/utils/http-utils.js +139 -0
- package/src/ui/utils/mime-types.js +196 -0
- package/src/ui/utils/slug.js +31 -0
- package/src/ui/vite.config.js +22 -0
- package/src/ui/watcher.js +28 -2
- package/src/utils/jobs.js +39 -0
- package/src/ui/dist/assets/index-DeDzq-Kk.js +0 -23863
- package/src/ui/dist/assets/style-aBtD_Yrs.css +0 -62
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Box, Flex, Table, Text
|
|
2
|
+
import { Box, Flex, Table, Text } from "@radix-ui/themes";
|
|
3
3
|
import { Progress } from "./ui/progress";
|
|
4
|
+
import { Button } from "./ui/button.jsx";
|
|
4
5
|
import { TimerReset, ChevronRight } from "lucide-react";
|
|
5
6
|
import { fmtDuration, jobCumulativeDurationMs } from "../utils/duration.js";
|
|
6
7
|
import { countCompleted } from "../utils/jobs";
|
|
@@ -234,8 +235,8 @@ export default function JobTable({ jobs, pipeline, onOpenJob }) {
|
|
|
234
235
|
<Table.Cell>
|
|
235
236
|
<Button
|
|
236
237
|
variant="ghost"
|
|
237
|
-
size="
|
|
238
|
-
className="opacity-0 group-hover:opacity-100 transition-opacity
|
|
238
|
+
size="sm"
|
|
239
|
+
className="opacity-0 group-hover:opacity-100 transition-opacity"
|
|
239
240
|
aria-label={`View details for ${jobTitle}`}
|
|
240
241
|
>
|
|
241
242
|
<ChevronRight className="h-4 w-4" />
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import React, { useState, useRef, useEffect } from "react";
|
|
2
2
|
import { useNavigate, useLocation, Link } from "react-router-dom";
|
|
3
|
-
import * as Tooltip from "@radix-ui/react-tooltip";
|
|
4
3
|
import { Box, Flex, Text, Heading, Link as RadixLink } from "@radix-ui/themes";
|
|
5
4
|
import { Button } from "./ui/button.jsx";
|
|
6
5
|
import Logo from "./ui/Logo.jsx";
|
|
7
6
|
import PageSubheader from "./PageSubheader.jsx";
|
|
8
7
|
import UploadSeed from "./UploadSeed.jsx";
|
|
9
|
-
import {
|
|
8
|
+
import { Code2, Upload, List } from "lucide-react";
|
|
10
9
|
import "./ui/focus-styles.css";
|
|
11
10
|
|
|
12
11
|
/**
|
|
@@ -85,153 +84,157 @@ export default function Layout({
|
|
|
85
84
|
}, [isUploadOpen]);
|
|
86
85
|
|
|
87
86
|
return (
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
87
|
+
<Box className="min-h-screen bg-gray-1">
|
|
88
|
+
{/* Skip to main content link for accessibility */}
|
|
89
|
+
<Box
|
|
90
|
+
as="a"
|
|
91
|
+
href="#main-content"
|
|
92
|
+
className="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 z-50 bg-primary text-primary-foreground px-3 py-2 rounded-md text-sm font-medium focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 transition-colors"
|
|
93
|
+
>
|
|
94
|
+
Skip to main content
|
|
95
|
+
</Box>
|
|
96
|
+
|
|
97
|
+
{/* Header */}
|
|
98
|
+
<Box
|
|
99
|
+
role="banner"
|
|
100
|
+
className="sticky top-0 z-20 border-b border-gray-300 bg-gray-1/80 backdrop-blur supports-[backdrop-filter]:bg-gray-1/60"
|
|
101
|
+
>
|
|
102
|
+
<Flex
|
|
103
|
+
align="center"
|
|
104
|
+
justify="between"
|
|
105
|
+
className={`mx-auto w-full ${maxWidth} px-4 sm:px-6 lg:px-8 py-4`}
|
|
106
|
+
gap="4"
|
|
95
107
|
>
|
|
96
|
-
|
|
97
|
-
|
|
108
|
+
{/* Left side: Navigation and title */}
|
|
109
|
+
<Flex align="center" className="min-w-0 flex-1">
|
|
110
|
+
{/* Logo */}
|
|
111
|
+
<Box
|
|
112
|
+
asChild
|
|
113
|
+
className="shrink-0"
|
|
114
|
+
style={{ width: "80px", height: "60px" }}
|
|
115
|
+
>
|
|
116
|
+
<Link
|
|
117
|
+
to="/"
|
|
118
|
+
aria-label="Go to homepage"
|
|
119
|
+
className="rounded focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500"
|
|
120
|
+
>
|
|
121
|
+
<Logo />
|
|
122
|
+
</Link>
|
|
123
|
+
</Box>
|
|
124
|
+
|
|
125
|
+
{/* App title - clickable to navigate to dashboard */}
|
|
126
|
+
<Box
|
|
127
|
+
asChild
|
|
128
|
+
className="shrink-0 cursor-pointer hover:bg-gray-3 rounded p-1 -m-1 transition-colors"
|
|
129
|
+
onClick={() => navigate("/")}
|
|
130
|
+
>
|
|
131
|
+
<Heading
|
|
132
|
+
size="6"
|
|
133
|
+
weight="medium"
|
|
134
|
+
className="text-gray-12 truncate"
|
|
135
|
+
>
|
|
136
|
+
<>
|
|
137
|
+
Prompt
|
|
138
|
+
<br />
|
|
139
|
+
Pipeline
|
|
140
|
+
</>
|
|
141
|
+
</Heading>
|
|
142
|
+
</Box>
|
|
143
|
+
</Flex>
|
|
144
|
+
|
|
145
|
+
{/* Center: Navigation */}
|
|
146
|
+
<nav
|
|
147
|
+
role="navigation"
|
|
148
|
+
aria-label="Main navigation"
|
|
149
|
+
className="hidden md:flex"
|
|
150
|
+
>
|
|
151
|
+
<Flex align="center" gap="6">
|
|
152
|
+
<RadixLink
|
|
153
|
+
href="/pipelines"
|
|
154
|
+
className={`text-sm font-medium transition-colors hover:text-blue-600 ${
|
|
155
|
+
isActivePath("/pipelines")
|
|
156
|
+
? "text-blue-600"
|
|
157
|
+
: "text-gray-11 hover:text-gray-12"
|
|
158
|
+
}`}
|
|
159
|
+
aria-current={isActivePath("/pipelines") ? "page" : undefined}
|
|
160
|
+
>
|
|
161
|
+
<Flex align="center" gap="2">
|
|
162
|
+
<List className="h-4 w-4" />
|
|
163
|
+
Pipelines
|
|
164
|
+
</Flex>
|
|
165
|
+
</RadixLink>
|
|
166
|
+
<RadixLink
|
|
167
|
+
href="/code"
|
|
168
|
+
className={`text-sm font-medium transition-colors hover:text-blue-600 ${
|
|
169
|
+
isActivePath("/code")
|
|
170
|
+
? "text-blue-600"
|
|
171
|
+
: "text-gray-11 hover:text-gray-12"
|
|
172
|
+
}`}
|
|
173
|
+
aria-current={isActivePath("/code") ? "page" : undefined}
|
|
174
|
+
>
|
|
175
|
+
<Flex align="center" gap="2">
|
|
176
|
+
<Code2 className="h-4 w-4" />
|
|
177
|
+
Help
|
|
178
|
+
</Flex>
|
|
179
|
+
</RadixLink>
|
|
180
|
+
</Flex>
|
|
181
|
+
</nav>
|
|
182
|
+
|
|
183
|
+
{/* Right side: Actions */}
|
|
184
|
+
<Flex align="center" gap="3" className="shrink-0">
|
|
185
|
+
{actions}
|
|
186
|
+
<Button
|
|
187
|
+
size="md"
|
|
188
|
+
variant="solid"
|
|
189
|
+
onClick={toggleUploadPanel}
|
|
190
|
+
aria-controls="layout-upload-panel"
|
|
191
|
+
aria-expanded={isUploadOpen}
|
|
192
|
+
>
|
|
193
|
+
<Upload className="h-4 w-4" />
|
|
194
|
+
<Text size="2" className="ml-2">
|
|
195
|
+
Upload Seed
|
|
196
|
+
</Text>
|
|
197
|
+
</Button>
|
|
198
|
+
</Flex>
|
|
199
|
+
</Flex>
|
|
200
|
+
</Box>
|
|
98
201
|
|
|
99
|
-
|
|
202
|
+
{/* Upload Panel */}
|
|
203
|
+
{isUploadOpen && (
|
|
100
204
|
<Box
|
|
101
|
-
|
|
102
|
-
|
|
205
|
+
id="layout-upload-panel"
|
|
206
|
+
ref={uploadPanelRef}
|
|
207
|
+
role="region"
|
|
208
|
+
aria-label="Upload seed file"
|
|
209
|
+
className="bg-blue-50"
|
|
103
210
|
>
|
|
104
211
|
<Flex
|
|
105
|
-
|
|
106
|
-
|
|
212
|
+
direction="column"
|
|
213
|
+
gap="3"
|
|
107
214
|
className={`mx-auto w-full ${maxWidth} px-4 sm:px-6 lg:px-8 py-4`}
|
|
108
|
-
gap="4"
|
|
109
215
|
>
|
|
110
|
-
{/*
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
style={{ width: "80px", height: "60px" }}
|
|
117
|
-
>
|
|
118
|
-
<Link
|
|
119
|
-
to="/"
|
|
120
|
-
aria-label="Go to homepage"
|
|
121
|
-
className="rounded focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500"
|
|
122
|
-
>
|
|
123
|
-
<Logo />
|
|
124
|
-
</Link>
|
|
125
|
-
</Box>
|
|
126
|
-
|
|
127
|
-
{/* App title - clickable to navigate to dashboard */}
|
|
128
|
-
<Box
|
|
129
|
-
asChild
|
|
130
|
-
className="shrink-0 cursor-pointer hover:bg-gray-3 rounded p-1 -m-1 transition-colors"
|
|
131
|
-
onClick={() => navigate("/")}
|
|
132
|
-
>
|
|
133
|
-
<Heading
|
|
134
|
-
size="6"
|
|
135
|
-
weight="medium"
|
|
136
|
-
className="text-gray-12 truncate"
|
|
137
|
-
>
|
|
138
|
-
<>
|
|
139
|
-
Prompt
|
|
140
|
-
<br />
|
|
141
|
-
Pipeline
|
|
142
|
-
</>
|
|
143
|
-
</Heading>
|
|
216
|
+
{/* Success Message */}
|
|
217
|
+
{seedUploadSuccess && (
|
|
218
|
+
<Box className="rounded-md bg-green-50 p-3 border border-green-200">
|
|
219
|
+
<Text size="2" className="text-green-800">
|
|
220
|
+
Job <strong>{seedUploadSuccess}</strong> created successfully
|
|
221
|
+
</Text>
|
|
144
222
|
</Box>
|
|
145
|
-
|
|
223
|
+
)}
|
|
146
224
|
|
|
147
|
-
{
|
|
148
|
-
<nav
|
|
149
|
-
role="navigation"
|
|
150
|
-
aria-label="Main navigation"
|
|
151
|
-
className="hidden md:flex"
|
|
152
|
-
>
|
|
153
|
-
<Flex align="center" gap="6">
|
|
154
|
-
<RadixLink
|
|
155
|
-
href="/code"
|
|
156
|
-
className={`text-sm font-medium transition-colors hover:text-blue-600 ${
|
|
157
|
-
isActivePath("/code")
|
|
158
|
-
? "text-blue-600"
|
|
159
|
-
: "text-gray-11 hover:text-gray-12"
|
|
160
|
-
}`}
|
|
161
|
-
aria-current={isActivePath("/code") ? "page" : undefined}
|
|
162
|
-
>
|
|
163
|
-
<Flex align="center" gap="2">
|
|
164
|
-
<Code2 className="h-4 w-4" />
|
|
165
|
-
Help
|
|
166
|
-
</Flex>
|
|
167
|
-
</RadixLink>
|
|
168
|
-
</Flex>
|
|
169
|
-
</nav>
|
|
170
|
-
|
|
171
|
-
{/* Right side: Actions */}
|
|
172
|
-
<Flex align="center" gap="3" className="shrink-0">
|
|
173
|
-
{actions}
|
|
174
|
-
<Tooltip.Root delayDuration={200}>
|
|
175
|
-
<Tooltip.Trigger asChild>
|
|
176
|
-
<Button
|
|
177
|
-
size="sm"
|
|
178
|
-
variant="default"
|
|
179
|
-
onClick={toggleUploadPanel}
|
|
180
|
-
aria-controls="layout-upload-panel"
|
|
181
|
-
aria-expanded={isUploadOpen}
|
|
182
|
-
>
|
|
183
|
-
<Upload className="h-4 w-4" />
|
|
184
|
-
<Text size="2" className="ml-2">
|
|
185
|
-
Upload Seed
|
|
186
|
-
</Text>
|
|
187
|
-
</Button>
|
|
188
|
-
</Tooltip.Trigger>
|
|
189
|
-
<Tooltip.Content side="bottom" sideOffset={5}>
|
|
190
|
-
<Text size="2">Upload seed file</Text>
|
|
191
|
-
</Tooltip.Content>
|
|
192
|
-
</Tooltip.Root>
|
|
193
|
-
</Flex>
|
|
225
|
+
<UploadSeed onUploadSuccess={handleSeedUploadSuccess} />
|
|
194
226
|
</Flex>
|
|
195
227
|
</Box>
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
direction="column"
|
|
208
|
-
gap="3"
|
|
209
|
-
className={`mx-auto w-full ${maxWidth} px-4 sm:px-6 lg:px-8 py-4`}
|
|
210
|
-
>
|
|
211
|
-
{/* Success Message */}
|
|
212
|
-
{seedUploadSuccess && (
|
|
213
|
-
<Box className="rounded-md bg-green-50 p-3 border border-green-200">
|
|
214
|
-
<Text size="2" className="text-green-800">
|
|
215
|
-
Job <strong>{seedUploadSuccess}</strong> created
|
|
216
|
-
successfully
|
|
217
|
-
</Text>
|
|
218
|
-
</Box>
|
|
219
|
-
)}
|
|
220
|
-
|
|
221
|
-
<UploadSeed onUploadSuccess={handleSeedUploadSuccess} />
|
|
222
|
-
</Flex>
|
|
223
|
-
</Box>
|
|
224
|
-
)}
|
|
225
|
-
|
|
226
|
-
{/* Main content */}
|
|
227
|
-
<main
|
|
228
|
-
id="main-content"
|
|
229
|
-
role="main"
|
|
230
|
-
className={`mx-auto w-full ${maxWidth} px-4 sm:px-6 lg:px-8`}
|
|
231
|
-
>
|
|
232
|
-
{children}
|
|
233
|
-
</main>
|
|
234
|
-
</Box>
|
|
235
|
-
</Tooltip.Provider>
|
|
228
|
+
)}
|
|
229
|
+
|
|
230
|
+
{/* Main content */}
|
|
231
|
+
<main
|
|
232
|
+
id="main-content"
|
|
233
|
+
role="main"
|
|
234
|
+
className={`mx-auto w-full ${maxWidth} px-4 sm:px-6 lg:px-8`}
|
|
235
|
+
>
|
|
236
|
+
{children}
|
|
237
|
+
</main>
|
|
238
|
+
</Box>
|
|
236
239
|
);
|
|
237
240
|
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import ReactMarkdown from "react-markdown";
|
|
3
|
+
import remarkGfm from "remark-gfm";
|
|
4
|
+
import rehypeHighlight from "rehype-highlight";
|
|
5
|
+
import "highlight.js/styles/github-dark.css";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* MarkdownRenderer component for rendering markdown content with syntax highlighting
|
|
9
|
+
* @param {Object} props - Component props
|
|
10
|
+
* @param {string} props.content - Markdown content to render
|
|
11
|
+
* @param {string} props.className - Additional CSS classes
|
|
12
|
+
*/
|
|
13
|
+
export function MarkdownRenderer({ content, className = "" }) {
|
|
14
|
+
const [copiedCode, setCopiedCode] = useState(null);
|
|
15
|
+
|
|
16
|
+
// Handle code copy
|
|
17
|
+
const handleCopyCode = async (code) => {
|
|
18
|
+
try {
|
|
19
|
+
await navigator.clipboard.writeText(code);
|
|
20
|
+
setCopiedCode(code);
|
|
21
|
+
setTimeout(() => setCopiedCode(null), 2000);
|
|
22
|
+
} catch (err) {
|
|
23
|
+
console.error("Failed to copy code:", err);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Custom code block component with copy button
|
|
28
|
+
const CodeBlock = ({ children, className: codeClassName }) => {
|
|
29
|
+
const language = codeClassName?.replace("language-", "") || "text";
|
|
30
|
+
const code = React.Children.toArray(children).join("");
|
|
31
|
+
const isCopied = copiedCode === code;
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="relative group">
|
|
35
|
+
<pre className="!bg-muted !text-foreground rounded-lg p-4 overflow-x-auto mt-3 mb-3">
|
|
36
|
+
<code className={codeClassName}>{children}</code>
|
|
37
|
+
</pre>
|
|
38
|
+
<button
|
|
39
|
+
onClick={() => handleCopyCode(code)}
|
|
40
|
+
className="absolute top-2 right-2 bg-muted-foreground/80 text-background text-xs px-2 py-1 rounded hover:bg-muted-foreground transition-opacity opacity-0 group-hover:opacity-100"
|
|
41
|
+
aria-label="Copy code to clipboard"
|
|
42
|
+
>
|
|
43
|
+
{isCopied ? "Copied!" : "Copy"}
|
|
44
|
+
</button>
|
|
45
|
+
{language !== "text" && (
|
|
46
|
+
<span className="absolute top-2 left-2 text-xs text-muted-foreground">
|
|
47
|
+
{language}
|
|
48
|
+
</span>
|
|
49
|
+
)}
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div
|
|
56
|
+
className={`!max-w-none prose prose-sm dark:prose-invert ${className}`}
|
|
57
|
+
>
|
|
58
|
+
<ReactMarkdown
|
|
59
|
+
remarkPlugins={[remarkGfm]}
|
|
60
|
+
rehypePlugins={[rehypeHighlight]}
|
|
61
|
+
components={{
|
|
62
|
+
code: CodeBlock,
|
|
63
|
+
h1: ({ children }) => (
|
|
64
|
+
<h1 className="text-xl font-bold mb-3 text-foreground">
|
|
65
|
+
{children}
|
|
66
|
+
</h1>
|
|
67
|
+
),
|
|
68
|
+
h2: ({ children }) => (
|
|
69
|
+
<h2 className="text-lg font-semibold mb-2 text-foreground">
|
|
70
|
+
{children}
|
|
71
|
+
</h2>
|
|
72
|
+
),
|
|
73
|
+
h3: ({ children }) => (
|
|
74
|
+
<h3 className="text-base font-medium mb-2 text-foreground">
|
|
75
|
+
{children}
|
|
76
|
+
</h3>
|
|
77
|
+
),
|
|
78
|
+
p: ({ children }) => (
|
|
79
|
+
<p className="mb-3 text-foreground leading-relaxed">{children}</p>
|
|
80
|
+
),
|
|
81
|
+
ul: ({ children }) => (
|
|
82
|
+
<ul className="list-disc pl-5 mb-3 space-y-1 text-foreground">
|
|
83
|
+
{children}
|
|
84
|
+
</ul>
|
|
85
|
+
),
|
|
86
|
+
ol: ({ children }) => (
|
|
87
|
+
<ol className="list-decimal pl-5 mb-3 space-y-1 text-foreground">
|
|
88
|
+
{children}
|
|
89
|
+
</ol>
|
|
90
|
+
),
|
|
91
|
+
li: ({ children }) => <li className="ml-2">{children}</li>,
|
|
92
|
+
a: ({ children, href }) => (
|
|
93
|
+
<a
|
|
94
|
+
href={href}
|
|
95
|
+
className="text-primary hover:text-primary/80 underline"
|
|
96
|
+
target="_blank"
|
|
97
|
+
rel="noopener noreferrer"
|
|
98
|
+
>
|
|
99
|
+
{children}
|
|
100
|
+
</a>
|
|
101
|
+
),
|
|
102
|
+
blockquote: ({ children }) => (
|
|
103
|
+
<blockquote className="border-l-4 border-primary/50 pl-4 py-2 my-3 bg-muted/30 italic text-foreground/80">
|
|
104
|
+
{children}
|
|
105
|
+
</blockquote>
|
|
106
|
+
),
|
|
107
|
+
table: ({ children }) => (
|
|
108
|
+
<div className="overflow-x-auto my-4">
|
|
109
|
+
<table className="min-w-full border-collapse border border-border text-foreground">
|
|
110
|
+
{children}
|
|
111
|
+
</table>
|
|
112
|
+
</div>
|
|
113
|
+
),
|
|
114
|
+
thead: ({ children }) => (
|
|
115
|
+
<thead className="bg-muted/50">{children}</thead>
|
|
116
|
+
),
|
|
117
|
+
tbody: ({ children }) => <tbody>{children}</tbody>,
|
|
118
|
+
tr: ({ children }) => (
|
|
119
|
+
<tr className="border-b border-border hover:bg-muted/20">
|
|
120
|
+
{children}
|
|
121
|
+
</tr>
|
|
122
|
+
),
|
|
123
|
+
th: ({ children }) => (
|
|
124
|
+
<th className="border border-border px-4 py-2 text-left font-semibold">
|
|
125
|
+
{children}
|
|
126
|
+
</th>
|
|
127
|
+
),
|
|
128
|
+
td: ({ children }) => (
|
|
129
|
+
<td className="border border-border px-4 py-2">{children}</td>
|
|
130
|
+
),
|
|
131
|
+
hr: () => <hr className="my-4 border-border" />,
|
|
132
|
+
strong: ({ children }) => (
|
|
133
|
+
<strong className="font-bold text-foreground">{children}</strong>
|
|
134
|
+
),
|
|
135
|
+
em: ({ children }) => (
|
|
136
|
+
<em className="italic text-foreground">{children}</em>
|
|
137
|
+
),
|
|
138
|
+
del: ({ children }) => (
|
|
139
|
+
<del className="line-through text-muted-foreground">{children}</del>
|
|
140
|
+
),
|
|
141
|
+
}}
|
|
142
|
+
>
|
|
143
|
+
{content}
|
|
144
|
+
</ReactMarkdown>
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export default MarkdownRenderer;
|